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

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 搜索:葫芦知识)。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,287评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,346评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,277评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,132评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,147评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,106评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,019评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,862评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,301评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,521评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,682评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,405评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,996评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,651评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,803评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,674评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,563评论 2 352

推荐阅读更多精彩内容