那曲檬骨新材料有限公司

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

    文章

    3399

    瀏覽量

    150979
  • node
    +關注

    關注

    0

    文章

    23

    瀏覽量

    5951
  • vue
    vue
    +關注

    關注

    0

    文章

    58

    瀏覽量

    7893
收藏 人收藏

    評論

    相關推薦

    用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

    Proxy、Reflect以及Vue30的應用原理概述

    ES6的Proxy、Reflect以及Vue30的應用原理
    發表于 07-26 17:10

    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定義

    Vue (讀音 /vju?/,類似于 view) 是一套用于構建用戶界面的漸進式JavaScript框架。 Vue 的核心庫只關注視圖層,也就是只處理頁面。 Vue提供的一套J
    的頭像 發表于 02-06 16:41 ?1121次閱讀
    <b class='flag-5'>Vue</b>入門之<b class='flag-5'>Vue</b>定義

    iOS中集成Vue是什么

    上一節Vue非瀏覽器環境下的嘗試我們利用了weexvue的dom實現成功的非瀏覽器環境
    的頭像 發表于 03-03 09:56 ?694次閱讀
    <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 ?792次閱讀

    簡述大前端技術棧的渲染原理

    應用開發:Android、iOS、鴻蒙(HarmonyOS)等; ?Web前端框架:Vue、React、Angular等; ?小程序開發:微信小程序、京東小程序、支付寶小程序等; ?跨平臺解決方案:React Native、Flutter、Taro、Weex等。 什么是
    的頭像 發表于 11-07 10:11 ?288次閱讀

    Vue3設計思想及響應式源碼剖析

    DOM進行了重寫、對模板的編譯進行了優化操作... 2、Vue3設計思想 ?Vue3.0更注重模塊上的拆分,2.0無法單獨使用部分模塊。需要引入
    的頭像 發表于 12-20 10:24 ?173次閱讀
    迪威百家乐赌场娱乐网规则| 百家乐官网网站可信吗| 平泉县| 大发888真人娱乐场网址官网| 神州百家乐的玩法技巧和规则| 天天百家乐的玩法技巧和规则| 哪家百家乐优惠最好且信誉不错| 百家乐群详解包杀| 百家乐开闲的几率多大| 大发888娱乐城真钱lm0| 大发888注册开户| 金都国际| 哪里有百家乐官网代理| 菲律宾百家乐官网太阳城| 百家乐官网怎样玩的| 金木棉百家乐官网的玩法技巧和规则| 百家乐代理网址| 百家乐币| 威尼斯人娱乐赌博| 永盈会娱乐场官网| 百家乐官网投注法则| 百家乐官网庄闲预测| 鼎龙百家乐官网的玩法技巧和规则 | 盈得利百家乐官网娱乐城| 哪个百家乐玩法平台信誉好| 红桃K百家乐的玩法技巧和规则| 威尼斯人娱乐城首选d77com | 赌博的危害| 百家乐官网的玩法技巧和规则 | 惠安县| 立即博百家乐官网娱乐城| 福布斯百家乐官网的玩法技巧和规则 | 八大胜百家乐现金网| 百家乐77scs| 豪门国际网上娱乐| 澳门百家乐官网真人版| 百家乐龙虎规则| 威尼斯人娱乐网可信吗| 旅游| 大丰收百家乐官网的玩法技巧和规则 | 网络百家乐怎样出千|