美文网首页weexWeex 从无到有开发一款上线应用
Weex 从无到有开发一款上线应用 3

Weex 从无到有开发一款上线应用 3

作者: 叫我小詺 | 来源:发表于2017-05-25 17:46 被阅读205次

    iOS调试Demo
    WeexDemo
    本篇将开始跟大家探讨如何进行Weex页面跟原生的交互,即Weex调原生方法,原生方法调取Weex方法。
    -------------2017.05.25 今晚写 敲了一天了--------
    -------------2017.05.26 胳膊疼 家里电脑什么环境也没弄,今晚就写了------------
    像Weex提供的navigator,animate等扩展都是通过两者交互实现的。
    接第二篇的页面实现。

    "我"界面完整UI.jpeg
    从Weex的扩展开始

    如何实现,点击Push新的页面?Weex提供了navigator扩展。
    来看WXNavigatorModule源码。
    首先他遵守WXModuleProtocol协议

    @protocol WXModuleProtocol <NSObject>
    /*多用于 Module 回调结果给 js,回调类型分为下面两种:
    
    WXModuleCallback 为了性能考虑,该回调只能回调通知js一次,之后会被释放,多用于一次结果
    
    WXModuleKeepAliveCallback 该回调可以设置是否为多次回调类型,多次回调的场景如持续监听位置的变化,并返回给 js
    */
    /**
    这个是声明了一个通用的Native回调JS的block 这个会在使用完之后被释放以来节约内存 */
    typedef void (^WXModuleCallback)(id result);
    /**
    这个是声明了一个通用的Native回调JS的block 这个会通过keepAlive参数来决定 使用完之后被释放以来节约内存
     */
    typedef void (^WXModuleKeepAliveCallback)(id result, BOOL keepAlive);
    /**
    该Moudule绑定的Instance。我们可以通过他在xxxModule.m来调用方法和属性
     */
    @property (nonatomic, weak) WXSDKInstance *weexInstance;
    @end
    

    上述协议当我们要扩展一个Module时,我们需要遵守这个协议。在xxxxModule.m中需要@synthesize weexInstance生成成员变量。
    那么我们如何将原生方法暴露给JS呢?通过WX_EXPORT_METHOD(@selector(xxxxx))这个宏来实现。
    WXNavigatorModule有一个方法:

    - (void)open:(NSDictionary *)param success:(WXModuleCallback)success failure:(WXModuleCallback)failure
    { 
    }
    

    那么就可以通过

    WX_EXPORT_METHOD(@selector(open:success:failure:))
    

    暴露给JS。
    那么WX_EXPORT_METHOD是如何实现的呢?

    #define WX_EXPORT_METHOD(method) WX_EXPORT_METHOD_INTERNAL(method,wx_export_method_)
    #define WX_EXPORT_METHOD_INTERNAL(method, token) \
    + (NSString *)WX_CONCAT_WRAPPER(token, __LINE__) { \
        return NSStringFromSelector(method); \
    }
    #define WX_CONCAT_WRAPPER(a, b)    WX_CONCAT(a, b)
    #define WX_CONCAT(a, b)   a ## b
    

    如上,首先会传递过来一个SEL参数,然后将wx_export_method_当前代码行数拼接成一个方法名如改宏写在第69行:wx_export_method_69
    具体方法为:

    + (NSString *)wx_export_method_69 { 
        return NSStringFromSelector(method); 
    }
    
    实现一个简单需求

    了解了如何扩展Module,那我们现在就从最简单的需求开始,点击某一行Cell Push一个新的界面。
    再来看WXNavigatorModule

    - (id<WXNavigationProtocol>)navigator
    {
        id<WXNavigationProtocol> navigator = [WXHandlerFactory handlerForProtocol:@protocol(WXNavigationProtocol)];
        return navigator;
    }
    - (void)open:(NSDictionary *)param success:(WXModuleCallback)success failure:(WXModuleCallback)failure
    {
    //这里 Weex源码中 使用了一个有默认实现WXNavigationProtocol的协议类,
    //并将它注册到WXHandlerFactory实例的handlers(这是一个线程安全字典:WXThreadSafeMutableDictionary 继承自NSMutableDictionary 目的是为了达到读写都在同一个并发队列)
        id<WXNavigationProtocol> navigator = [self navigator];
        UIViewController *container = self.weexInstance.viewController;
        if (navigator && [navigator respondsToSelector:@selector(open:success:failure:withContainer:)]) {
    //这样通过注册的默认实现来响应这个方法 这里我们可以不去考虑他的内部实现,依照我们平时Push的方法书写下面代码就可以
            [navigator open:param success:success failure:failure withContainer:container];
        }
    }
    

    weex 帮我们实现了一个常用的配置导航栏的接口:

    Weex导航栏接口.png
    到这里我们已经看到了Weex的WXNavigatorModule是如何开放接口给JS,现在我们就可以自己写一个Module
    现在我们自己扩展一个Module,以iOS调试Demo中的XMWXModule为例,我们自己实现一个Push新页面的方法。
    WX_EXPORT_METHOD(@selector(openURL:options:completionHandler:))
    -(void)openURL:(NSString *)url options:(NSDictionary<NSString *,id> *)options completionHandler:(WXCallback)completion
    {
        NSString *newURL = url;
        if ([url hasPrefix:@"//"]) {
            newURL = [NSString stringWithFormat:@"http:%@", url];
        } else if (![url hasPrefix:@"http"]) {
            newURL = [NSURL URLWithString:url relativeToURL:self.weexInstance.scriptURL].absoluteString;
        }
        XMWXViewController * controller = [[XMWXViewController alloc] init];
        controller.renderURL = [NSURL URLWithString:newURL];
    //从option参数中去取出navigtionBarInfo的数据
        if ([options objectForKey:@"navigtionBarInfo"]) {
            controller.renderInfo = [XMWXNavigationItem infoWithDict:[options objectForKey:@"navigtionBarInfo"]];
        }
    
        [self.weexInstance.viewController showViewController:controller sender:nil];
        completion(@{@"result":@"success"});
    }
    

    到这第一步的扩展Module已经可以了,现在就去注册我们的Module,在Weex配置方法中加入

    [WXSDKEngine registerModule:@"XMWXModule" withClass:NSClassFromString(@"XMWXModule")];
    
    如何使用扩展Module

    以个人主页的ViewController为例FifthViewController.vue
    Weex 从无到有开发一款上线应用 2中已经说了如何做一个Cell,现在我们就为Cell加上点击事件,调取我们的方法
    实现如下:

    <script>
    
    //如何实现点击事件,
    //1. 首先引入我们在原生注册的Module名字
        let appBasicModule = weex.requireModule('XMWXModule')
        export default {
            props: {
    //        cell的Model数据
                item: {
                    type: Object,
                    default: 'null'
                },
    //        因为点赞和余额的DetailTextLabel的颜色不一样,需要给其一个判断条件
                isMark: {//这个cell是不是点赞Cell
                    type: Boolean,
                    default: false
                }
            },
            data () {
                return {}
            },
            methods: {
    //      2.实现点击事件方法
                goPage()
                {
    //      3.配置导航栏信息
                    var navigtionBarInfo = {
                        title: this.item.navigaitonBarTitle,
                        clearTitleColor:'333333',
                        blurTitleColor:'333333',
                        clearNavigationBar:true,
                        hiddenNavgitionBar:false,
                        navigationBarBackgroundColor:'',
                        navgationBarBackgroundImage:'',
                        customTitleViewURL:'',
                    };
    //    4.调用原生方法
                    appBasicModule.openURL(this.item.actionUrl,{//这里的actionUrl就是我们每一个ViewController对应的JSBundle文件地址
                        navigtionBarInfo:navigtionBarInfo,
                    },function (result) {
                        
                    })
                }
            }
        }
    </script>
    

    现在我们的个人主页,可以点击然后Push 到新的页面。效果如下:
    ![个人页面.jpeg](http://upload-images.jian

    Native 调取JS

    先聊需求,假设我们iOS有一个比较好的下拉加载控件,但是安卓的同事没能实现出来,那么我们无法将这个扩展成Component(因为两端不通用),那我们该如何操作这部分逻辑呢?首先,考虑到我们当前的页面一定是Weex实现的,那么网络请求也一定是在Vue文件中。所以当原生的刷新控件要调用beginRefresh是就需要调用JS的refresh方法。
    先暴露JS方法给Native.

    <script>
    // 1 获取全局响应Module
        let globalEvent = weex.requireModule('globalEvent');
            created () {
                let self = this;
                self.day = new Date().getDate();
                this.host=this.getHost().replace('8081','8083')
                try {
                    appBasicModule.accessKeyWithCallback(function (accessKey) {
                        self.accessKey = accessKey;
                    });
                    appBasicModule.userIdWithCallback(function (userId) {
                        self.userId = userId
                        self.getAllData();
                    })
                }
                catch (e) {
                    self.getAllData();
                }
            },
            mounted(){
                let self = this;
    // 监听调取JS的信号名,类似于通知中心 这个地方就是注册原生发送的refresh通知 
    // refresh就是原生的通知名,后边跟着的就是需要收到通知需要做的事情
                globalEvent.addEventListener("refresh", function (e) {
    //            刷新页面 重新请求数据等
                });
            }
    </script>
    

    原生端需要使用这些方法:

    /**
      * @abstract Fire an event to the component and tell Javascript which value has been changed. 
      * @param eventName 事件名称,可以在weex文件某个标签组件监听,命名规范为 onXXX
      * @param params 数据
      * @param domChanges 发生改变的数据
      **/
    - (void)fireEvent:(NSString *)eventName params:(NSDictionary *)params domChanges:(NSDictionary *)domChanges
    
    /**
     * fire module event;
     */
    - (void)fireModuleEvent:(Class)module eventName:(NSString *)eventName params:(NSDictionary*)params;
    
    /**
     * fire global event
     */
    - (void)fireGlobalEvent:(NSString *)eventName params:(NSDictionary *)params;
    
    

    Weex文档有这方面描述。
    这里补充的是用实例InstanceglobalEvent来操作。

    //添加刷新
    if (!scrollView.mj_header) {
        scrollView.mj_header = [HLCustomRefreshHeader headerWithRefreshingBlock:^{
            //调取JS刷新方法
            if (self.segmentedControl.selectedSegmentIndex == 0) {
                [self.guanzhuInstance fireGlobalEvent:@"refresh" params:nil];
            }else
            {
                [self.guangchangInstance fireGlobalEvent:@"refresh" params:nil];
            }
            resetScrollViewSomePro(scrollView);
        }];
        indestance.endRefreshBlock = ^{
            @strongify(scrollView);
            if (scrollView.mj_header.state != MJRefreshStateIdle) {
                
                [scrollView.mj_header setState:MJRefreshStateIdle];
                resetScrollViewSomePro(scrollView);
            }
        };
    

    这样就达到了Native调用JS的目的(Demo中没有使用这种方式,这是我们公司应用针对需求写的逻辑,可以下载App看一下:Applestore 搜索:葫芦知识)。

    相关文章

      网友评论

        本文标题:Weex 从无到有开发一款上线应用 3

        本文链接:https://www.haomeiwen.com/subject/rzyzxxtx.html