0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看威廉希尔官方网站 视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

在iOS中渲染vue与事件处理是什么

汽车电子威廉希尔官方网站 来源:程序猿搬砖 作者:程序猿搬砖 2023-03-03 09:55 次阅读

上一节我们已经完成了在iOS中集成vue,并成功拿到了创建Node的数据回调,这一节我们来完成Node的建立与渲染,并完成事件支持。

「第一步: 定义Node节点的数据结构」

具体定义如下:

@interface DomNode : NSObject
/// DomNode的标识符
@property (nonatomic, copy)NSString *ref;
/// 节点的类型(这里暂时定义四种,满足Demo的需要就可以了)
@property (nonatomic, assign)DomNodeType type;
/// 节点的渲染属性,需要在渲染的时候展示出来的(其中有一部分是与布局属性重合的:即在布局属性里面也需要在渲染属性里面)
@property (nonatomic, strong)DomAttribute *attribute;
/// 节点的布局属性,用于Flex布局计算
@property (nonatomic, strong)DomStyle *style;
/// 父节点
@property (nonatomic, weak)DomNode *parent;
/// 子节点
@property (nonatomic, strong)NSMutableArray

在这个数据结构中DomStyle是用于参与布局计算的, DomAttribute用于渲染。

他们的具体数据结构如下:

@interface DomStyle : NSObject
@property (nonatomic, assign) YGDirection direction;
@property (nonatomic, assign) YGFlexDirection flexDirection;
@property (nonatomic, assign) YGJustify justifyContent;
@property (nonatomic, assign) YGAlign alignSelf;
@property (nonatomic, assign) YGAlign alignItems;
@property (nonatomic, assign) YGPositionType positionType;
@property (nonatomic, assign) YGWrap flexWrap;
@property (nonatomic, assign) YGOverflow overflow;
@property (nonatomic, assign) YGDisplay display;
@property (nonatomic, assign) int flex;
@property (nonatomic, assign) int flexGrow;
@property (nonatomic, assign) int flexShrink;
@property (nonatomic, assign) DomEdge position;
@property (nonatomic, assign) DomEdge margin;
@property (nonatomic, assign) DomEdge padding;
@property (nonatomic, strong) DomBorder *border;
@property (nonatomic, assign) CGFloat height;
@property (nonatomic, assign) CGFloat width;
@property (nonatomic, assign) CGFloat maxWidth;
@property (nonatomic, assign) CGFloat minWidth;
@property (nonatomic, assign) CGFloat maxHeight;
@property (nonatomic, assign) CGFloat minHeight;

- (instancetype)initWithData:(NSDictionary *)data;
- (void)updateStyleWithData:(NSDictionary * _Nullable)data;
- (void)fill:(YGNodeRef)ygNode;
@end

style中的数据结构比较简单,需要注意的是在初始化相关属性时,需要与Yoga定义的YGNodeRef中的数据结构初始化值一致,因为我们在fill方法会把所有支持的属性全部同步到YGNodeRef

updateStyleWithDatainitWithData所传递进来的则是从vue中拿到的回调数据,并将他们解析成对应的属性值。

具体的实现代码,我会附加在最后。

@interface DomAttribute : NSObject
@property (nonatomic, strong) NSString *color;
@property (nonatomic, strong) NSString *backgroundColor;
@property (nonatomic, assign) NSInteger fontSize;
@property (nonatomic, strong) NSString *fontFamily;
@property (nonatomic, strong) NSString *value;
@property (nonatomic, strong) NSString *imageNamed;
@property (nonatomic, assign) NSInteger maxNumberLine;
@property (nonatomic, strong) DomBorder *border;

- (instancetype)initWithData:(NSDictionary *)data;
- (void)updateAttributeWithData:(NSDictionary * _Nullable)data;
@end

这里需要注意的是,某些数据不仅参与计算,还参与渲染,比如: border

其他的数据结构定义的实现代码,我会附加在最后。

「第二:构建渲染树」

定义好Node所需要的数据结构之后,我们就可以将回调数据解析成一个Node Tree了。

- (void)_handleCallNativeCallback:(NSString *)instanceId data:(NSDictionary * _Nonnull)data {
    if(!data) return;
    NSDictionary *info = data[@"0"];
    if(!info || ![info isKindOfClass:[NSDictionary class]]) return;
    NSString *method = info[@"method"];
    if(method.length == 0) return;
    if([method isEqualToString:@"createBody"]) {
        [self _createBody:instanceId data:info];
    } else if([method isEqualToString:@"addElement"]) {
        [self _addElement:instanceId data:info];
    } else if([method isEqualToString:@"updateAttrs"]) {
        [self _updateAttrs:info];
    } else if([method isEqualToString:@"updateStyle"]) {
        [self _updateStyles:info];
    } else if([method isEqualToString:@"createFinish"]) {
        [self _createFinished];
    } else {
        NSLog(@"data: %@", data);
    }
}

具体方法实现代码,附加在后面。

通过对callNative的处理,在createFinished时构建好Node Tree。

「第三:完成布局前的准备工作」

构建好Node Tree,就可以通知Yoga,可以开始计算布局了。

在通知Yoga之后,需要将属性映射到YGNodeRef Tree

- (void)fill {
    [self.style fill:_ygNode];
    for(DomNode *child in _children) {
        [child fill];
    }
    _dirty = NO;
}

通过从根节点Node深度遍历调用fill方法,将数据映射到YGNodeRef,这里需要注意的是,具体的fill方法是在style中实现的,因为只有style里面的属性会参与计算。

具体的实现代码如下:

- (void)fill:(YGNodeRef)ygNode {
    YGNodeStyleSetDirection(ygNode, _direction);
    YGNodeStyleSetDisplay(ygNode, _display);
    YGNodeStyleSetFlexDirection(ygNode, _flexDirection);
    YGNodeStyleSetJustifyContent(ygNode, _justifyContent);
    YGNodeStyleSetAlignSelf(ygNode, _alignSelf);
    YGNodeStyleSetAlignItems(ygNode, _alignItems);
    YGNodeStyleSetPositionType(ygNode, _positionType);
    YGNodeStyleSetFlexWrap(ygNode, _flexWrap);
    YGNodeStyleSetOverflow(ygNode, _overflow);
    YGNodeStyleSetFlex(ygNode, _flex);
    YGNodeStyleSetFlexGrow(ygNode, _flexGrow);
    YGNodeStyleSetFlexShrink(ygNode, _flexShrink);
    if(_width >= 0) YGNodeStyleSetWidth(ygNode, _width);
    if(_height >= 0) YGNodeStyleSetHeight(ygNode, _height);
    if(_minWidth >= 0) YGNodeStyleSetMinWidth(ygNode, _minWidth);
    if(_minHeight >= 0) YGNodeStyleSetMinHeight(ygNode, _minHeight);
    if(_maxWidth >= 0) YGNodeStyleSetMaxWidth(ygNode, _maxWidth);
    if(_maxHeight >= 0) YGNodeStyleSetMinWidth(ygNode, _maxHeight);
    YGNodeStyleSetBorder(ygNode, YGEdgeAll, _border.width);
    /// Padding
    if(self.padding.left >= 0)     YGNodeStyleSetPadding(ygNode, YGEdgeLeft, self.padding.left);
    if(self.padding.top >= 0)      YGNodeStyleSetPadding(ygNode, YGEdgeTop, self.padding.top);
    if(self.padding.right >= 0)    YGNodeStyleSetPadding(ygNode, YGEdgeRight, self.padding.right);
    if(self.padding.bottom >= 0)   YGNodeStyleSetPadding(ygNode, YGEdgeBottom, self.padding.bottom);
    /// Margin
    if(self.margin.left >= 0)      YGNodeStyleSetMargin(ygNode, YGEdgeLeft, self.margin.left);
    if(self.margin.top >= 0)       YGNodeStyleSetMargin(ygNode, YGEdgeTop, self.margin.top);
    if(self.margin.right >= 0)     YGNodeStyleSetMargin(ygNode, YGEdgeRight, self.margin.right);
    if(self.margin.bottom >= 0)    YGNodeStyleSetMargin(ygNode, YGEdgeBottom, self.margin.bottom);
    /// Position
    if(self.position.left >= 0)    YGNodeStyleSetPosition(ygNode, YGEdgeLeft, self.position.left);
    if(self.position.top >= 0)     YGNodeStyleSetPosition(ygNode, YGEdgeTop, self.position.top);
    if(self.position.right >= 0)   YGNodeStyleSetPosition(ygNode, YGEdgeRight, self.position.right);
    if(self.position.bottom >= 0)  YGNodeStyleSetPosition(ygNode, YGEdgeBottom, self.position.bottom);
}

构建好YGNodeRef Tree之后就可以进行布局的计算了

CGSize screenSize = self.view.bounds.size;
YGNodeCalculateLayout(ygNode, screenSize.width, screenSize.height, YGNodeStyleGetDirection(ygNode));

通过调用以上接口,计算好每个元素的位置与大小。

这里需要注意的是,screenSize并不是一定要传递屏幕大小,我们需要渲染到的目标视图是多大,就传递多大。

在这里我们刚好使用了整个屏幕

「第四:开始渲染」

完成布局计算后,就开始对Node进行渲染了,代码很简单:

由于是测试代码,所以只是简单的完成了渲染,没有进行优化。

实际上这里应该将不同节点在原生对应的元素定义出来,通过元素内部的方法进行循环渲染,使代码结构更简单。

- (void)_render:(DomNode *)node superView:(UIView *)superView {
    if(!node) return;
    for(DomNode *child in node.children) {
        UIView *childView = NULL;
        if(child.type == DomNodeTypeLabel) {
            UILabel *label = [[UILabel alloc] init];
            label.font = [UIFont systemFontOfSize:child.attribute.fontSize];
            label.textColor = [UIColor colorWithHexString:child.attribute.color alpha:1.0f];
            label.text = child.attribute.value;
            childView = label;
        } else if(child.type == DomNodeTypeView) {
            UIView *view = [[UIView alloc] init];
            view.backgroundColor = [UIColor colorWithHexString:child.attribute.backgroundColor alpha:1.0f];
            childView = view;
        } else if(child.type == DomNodeTypeButton) {
            UIButton *button = [[UIButton alloc] init];
            [button setTitle:child.attribute.value forState:UIControlStateNormal];
            [button setTitleColor:[UIColor colorWithHexString:child.attribute.color alpha:1.0f] forState:UIControlStateNormal];
            button.titleLabel.font = [UIFont systemFontOfSize:child.attribute.fontSize];
            childView = button;
        }
        childView.frame = child.rect;
        childView.backgroundColor = [UIColor colorWithHexString:child.attribute.backgroundColor alpha:1.0f];
        [superView addSubview:childView];
        childView.node = child;
        if(child.events.count > 0) {
            for(NSString *event in child.events) {
                if([event isEqualToString:@"click"]) {
                    childView.userInteractionEnabled = YES;
                    UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(_clickEvent:)];
                    [childView addGestureRecognizer:tap];
                }
            }
        }
        
        if(child.children.count > 0) {
            [self _render:child superView:childView];
        }
    }
}

完成渲染之后,是这样一个效果:

图片

「第五:处理事件」

样式的渲染不是一层不变的,最容易想到的就是事件会改变数据的状态,那么事件怎么传递给vue呢。

vue-weex-framework在加载之后,会在globalObject上挂载一个方法__WEEX_CALL_JAVASCRIPT__,通过JSContext来调用这个方法,将事件与事件挂载的元素id传递过去,就完成了在vue内部的事件调用。

代码如下:

- (void)sendEvent:(NSString *)ref event:(NSString *)event {
    NSLog(@"IOS Context收到事件: %@, %@", ref, event);
    NSDictionary *params = @{
        @"module": @"",
        @"method": @"fireEvent",
        @"args": @[
            ref,
            event
        ]
    };
    NSArray *args = @[@"1", @[params]];
    [[_context globalObject] invokeMethod:@"__WEEX_CALL_JAVASCRIPT__" withArguments:args];
}

完成了事件的渲染,我们来看看具体的效果

图片

**「这里有一个点需要注意一下:

1.当数据发生变化的时候,怎么让原生感知它的变化呢,这里我使用了CADisplayLink,每一帧都去检测一下Node Tree是否已经发生改变,如果有节点发生改变,就需要重新计算。

庆幸的是Yoga在内部是有缓存的,当我们标记了某一个节点需要重新计算后,Yoga会去判断哪些相关节点需要重新计算,不需要计算的则不会再计算了。

这样就会大大减少数据更新计算布局的时间了。

2.如果使用div来显示文本,在数据发生改变时不会调用updateAttrs,需要使用text标签显示会发生改变的文本信息

」**

到这里,我们基本上完成了从vue到渲染成原生的所有步骤,当然里面还有一些细节是没有处理好的,比如在加载vue模板的时候还可以传递一个json数据进去作为从原生代入的初始数据。

整体的骨架已经有了,感兴趣的朋友优化骨架完善细节就是接下来。

「总结:」

这个小系列分为三个小节,实例了一个有基本骨架结构的渲染vue代码的引擎:

1.完成从vue开发到打包成非浏览器环境使用的代码,完成vue-js-framework打包

2.将打包好的framework与vue模板代码集成到iOS当中

3.完成渲染与事件处理

写到最后:

本文章以iOS平台为宿主环境,很容易的你能想到将这个引擎扩展到android,或者更多的平台。

「附加资料:」

iOS-Vue-Demo: https://github.com/czqasngit/iOS-Vue-Demo

vue: https://cn.vuejs.org/

weex-framework:

https://github.com/apache/incubator-weex

webpack:

https://webpack.js.org/

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • iOS
    iOS
    +关注

    关注

    8

    文章

    3395

    浏览量

    150569
  • node
    +关注

    关注

    0

    文章

    23

    浏览量

    5934
  • vue
    vue
    +关注

    关注

    0

    文章

    58

    浏览量

    7839
收藏 人收藏

    评论

    相关推荐

    用WEB威廉希尔官方网站 栈开发NATIVE应用(二):WEEX 前端SDK原理详解

    的整体架构:可以看到JS-Native Bridge将渲染指令发送给Android或者iOS渲染引擎之前,我们的业务代码运行在JSCore/v8的执行引擎之中,而在该执行引擎之中除了
    发表于 03-02 14:10

    Linux搭建Vue开发环境

    本文介绍Linux环境下从零开始搭建Vue开发环境的整个过程,包括vue的安装,webstorm 安装配置,devtools的安装。
    发表于 07-24 06:20

    vue-router的概念和用法

    vue:前端路由和vue-router
    发表于 03-06 13:28

    12vue的插槽

    12vue插槽(slot)
    发表于 05-07 08:15

    vue的路由router是什么

    vue的路由router
    发表于 05-20 07:10

    Vue父组件与子组件之间的数据传递

    Vue父组件(vue实例)与子组件(component)之间的数据传递
    发表于 06-01 17:28

    vue-cli-----vue实例template:'<App/>是什么意思?

    哪位大神知道vue-cli-----vue实例template:'是什么意思吗?
    发表于 11-05 07:02

    vue全局变量的设置与组件修改全局变量的方法?

    vue全局变量的设置与组件修改全局变量的方法
    发表于 11-06 06:43

    前端渲染引擎的优势分析

    React、Vue、Angular等均属于MVVM模式,一些只需完成数据和模板简单渲染的场合,显得笨重且学习成本较高,而解决该问题非常优秀框架之一是doT.js,本文将对它进行详解。 背景 前端
    发表于 09-30 13:14 0次下载
    前端<b class='flag-5'>渲染</b>引擎的优势分析

    Vue入门Vue的生命周期

    .生命周期 4.1生命周期是什么 Vue的生命周期, 就是Vue实例从创建到销毁的过程.
    的头像 发表于 02-06 16:16 857次阅读
    <b class='flag-5'>Vue</b>入门<b class='flag-5'>Vue</b>的生命周期

    Vue入门之Vue定义

    Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式JavaScript框架。 Vue 的核心库只关注视图层,也就是只处理页面。 Vue提供的一套J
    的头像 发表于 02-06 16:41 1077次阅读
    <b class='flag-5'>Vue</b>入门之<b class='flag-5'>Vue</b>定义

    iOS中集成Vue是什么

    上一节Vue非浏览器环境下的尝试我们利用了weexvue的dom实现成功的非浏览器环境
    的头像 发表于 03-03 09:56 648次阅读
    <b class='flag-5'>在</b><b class='flag-5'>iOS</b>中集成<b class='flag-5'>Vue</b>是什么

    简单介绍一下Vue的响应式原理

    自从 Vue 发布以来,就受到了广大开发人员的青睐,提到 Vue,我们首先想到的就是 Vue 的响应式系统,那响应式系统到底是怎么回事呢?
    的头像 发表于 03-13 10:11 743次阅读

    简述大前端威廉希尔官方网站 栈的渲染原理

    应用开发:Android、iOS、鸿蒙(HarmonyOS)等; •Web前端框架:Vue、React、Angular等; •小程序开发:微信小程序、京东小程序、支付宝小程序等; •跨平台解决方案:React Native、Flutter、Taro、Weex等。 什么是
    的头像 发表于 11-07 10:11 207次阅读

    Vue3设计思想及响应式源码剖析

    DOM进行了重写、对模板的编译进行了优化操作... 2、Vue3设计思想 •Vue3.0更注重模块上的拆分,2.0无法单独使用部分模块。需要引入
    的头像 发表于 12-20 10:24 60次阅读