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

上一节我们已经完成了在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;@endstyle中的数据结构比较简单,需要注意的是在初始化相关属性时,需要与yoga定义的ygnoderef中的数据结构初始化值一致,因为我们在fill方法会把所有支持的属性全部同步到ygnoderef。
updatestylewithdata与initwithdata所传递进来的则是从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/

DDR5内存市场逐渐成熟 DDR4惨遭抛弃
纳米压印光刻,能让国产绕过ASML吗?
新公司将研发出具备竞争力且符合中国法规的燃料电池系统
分析苹果的生产线回到美国的可能性分析和介绍
热轧钢管在线检测技术的难题解决
在iOS中渲染vue与事件处理是什么
华为预计今年全球营收将超600亿美元 同比涨29%
气象监测站——气象观测有妙招
华为“元宇宙”相关专利曝光!从英伟达“数字人”到华为“星光巨塔”,巨头态度各不同
国星光电成为“中国质造”的行业典范
印度安防市场潜力无限 或将成中国安企下一个淘金之地
应对现代RF测试挑战,开创信号发生新时代就是干!
开关电源控制ICDK912原边反激式电源成熟方案低成本应用
Imagination通过IMG DXT GPU 为所有移动游戏玩家带来可扩展的高级光追技术
联发科业绩创新高 发4个月奖金激励员工
以太网标准与驱动系统设计
韩国计划在未来10年投资91亿元用于开发新的半导体材料和器件
单通道112G/s高速数字信号处理Retimer芯片Screaming Eagle
天然气流量测量中流量计的相关解决方案
优质磁环对于音响有哪些影响?