iOS调试Demo
WeexDemo
本篇将开始跟大家探讨如何进行Weex页面跟原生的交互,即Weex调原生方法,原生方法调取Weex方法。
-------------2017.05.25 今晚写 敲了一天了--------
-------------2017.05.26 胳膊疼 家里电脑什么环境也没弄,今晚就写了------------
像Weex提供的navigator,animate等扩展都是通过两者交互实现的。
接第二篇的页面实现。
从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的
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文档有这方面描述。
这里补充的是用实例Instance
和globalEvent
来操作。
//添加刷新
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 搜索:葫芦知识)。