Weex中页面导航的实现

2018-03-01 11:02:57来源:segmentfault作者:三豊人点击

分享

Weex为我们提供了navigator模块来控制页面的导航。Navigator模块究竟是怎么运作的,官方没有给我们一个感性的认识。本文旨在探究weex的导航机制,然后实现一个DEMO供参考。


推入一个页面,类似原生的pushViewController:animated:`。文档告诉我们这么做:


navigator.push({
url: 'http://dotwe.org/raw/dist/519962541fcf6acd911986357ad9c2ed.js',
animated: "true"
})

顺藤摸瓜,找到原生模块WXNavigatorModule,push方法是这样定义的:


- (void)push:(NSDictionary *)param callback:(WXModuleCallback)callback {
id<WXNavigationProtocol> navigator = [self navigator];
UIViewController *container = self.weexInstance.viewController;
[navigator pushViewControllerWithParam:param completion:^(NSString *code, NSDictionary *responseData) {
if (callback && code) {
callback(code);
}
} withContainer:container];
}

核心代码在WXNavigationProtocol协议的默认实现中。于是乎我们切换到官方实现类WXNavigationDefaultImpl,关键的代码都在这里了。不出所料,是基于UINavigationController的。


WXBaseViewController *vc = [[WXBaseViewController alloc] initWithSourceURL:[NSURL URLWithString:param[@"url"]]];
vc.hidesBottomBarWhenPushed = YES;
[container.navigationController pushViewController:vc animated:animated];
[self callback:block code:MSG_SUCCESS data:nil];

按照weex的设计原则,主视图会附加在一个UIViewController上。由这个UIViewController控制页面的加载和展示。承载weex页面的控制器,必须包含在一个UINavigationController中,否则导航无效。


Weex提供了基础的容器控制器类WXBaseViewController。在初始化时提供javascript代码的地址,它会从这个地址获取代码并展示页面。WXNavigatorModule默认使用WXBaseViewController来展示新的页面。我们可能需要对导航进行定制,或者用一个我们自己实现的控制器代替官方版本。只需要两步就可以做到:

实现自己的weex容器控制器。
实现自己的WXNavigationProtocol协议类,替换官方版本。

我实现了WXViewController。这里偷个懒,直接继承官方的。我在新控制器中加入了自动刷新逻辑。你会好奇我为什么不调用super的viewDidLoad方法?这是因为父类的实现中会隐藏导航栏(而且还有动画),我不想要这样的效果,也不明白这么设计的作用是什么。于是就通过子类覆盖了这个逻辑。


@interface WXViewController (Private)@property (nonatomic, strong) NSURL *sourceURL;- (void)_renderWithURL:(NSURL *)sourceURL;@[email protected] WXViewController () <SRWebSocketDelegate>@property (nonatomic, strong) SRWebSocket *hotReloadSocket;@[email protected] WXViewController- (void)dealloc {
#if DEBUG
[self.hotReloadSocket close];
#endif
}- (void)viewDidLoad {
void (*viewDidLoad)(id, SEL) = (void (*)(id, SEL))class_getMethodImplementation([UIViewController class], @selector(viewDidLoad));
viewDidLoad(self, @selector(viewDidLoad));self.view.backgroundColor = [UIColor whiteColor];
self.automaticallyAdjustsScrollViewInsets = NO;
[self _renderWithURL:self.sourceURL];#if DEBUG
NSString *hotReloadURL = @"ws://127.0.0.1:8082";
if (hotReloadURL){
_hotReloadSocket = [[SRWebSocket alloc] initWithURL:[NSURL URLWithString:hotReloadURL]];
_hotReloadSocket.delegate = self;
[_hotReloadSocket open];
}
#endif
}- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message {
if ([@"refresh" isEqualToString:message]) {
[self refreshWeex];
}
}@end

接下来就是WXNavigationImpl了。我只需要修改一个方法,于是同样选择了继承WXNavigationDefaultImpl。这个类的头文件没有公开怎么办?拷贝WXNavigationDefaultImpl.h到自己的项目就行啦。下面是我的实现,实际上只替换了容器控制器,其他代码不变。


@interface WXNavigationImpl (Private)- (void)callback:(WXNavigationResultBlock)block code:(NSString *)code data:(NSDictionary *)reposonData;@[email protected] WXNavigationImpl- (void)pushViewControllerWithParam:(NSDictionary *)param completion:(WXNavigationResultBlock)block withContainer:(UIViewController *)container {
if (0 == [param count] || !param[@"url"] || !container) {
[self callback:block code:MSG_PARAM_ERR data:nil];
return;
}BOOL animated = YES;
NSString *obj = [[param objectForKey:@"animated"] lowercaseString];
if (obj && [obj isEqualToString:@"false"]) {
animated = NO;
}WXViewController *vc = [[WXViewController alloc]initWithSourceURL:[NSURL URLWithString:param[@"url"]]];
vc.hidesBottomBarWhenPushed = YES;
[container.navigationController pushViewController:vc animated:animated];
[self callback:block code:MSG_SUCCESS data:nil];}@end

然后,在[WXSDKEngine initSDKEnvironment]调用后替换默认的handler:


[WXSDKEngine registerHandler:[WXNavigationImpl new] withProtocol:@protocol(WXNavigationProtocol)];

设置根视图控制器,这里我们使用了UINavigationController保证导航能够被支持。


NSURL *url = [NSURL URLWithString:@"http://127.0.0.1:8081/index.weex.js"];
UIViewController *demo = [[WXViewController alloc] initWithSourceURL:url];
[[UIApplication sharedApplication] delegate].window.rootViewController = [[UINavigationController alloc] initWithRootViewController:demo];

在index.vue代码中,添加一个按钮到屏幕中间,然后绑定一个点击事件。这里为了演示的目的,我们选择跳转到同样的地址。


onclick: function (e) {
const navigator = weex.requireModule('navigator')
navigator.push({
url: 'http://127.0.0.1:8081/index.weex.js'
})
}

我们启动打包服务器weex preview index.vue,编译运行iOS项目试一下,完美!


可是我的手又痒了,想要在导航栏上添加一个按钮。我在WXNavigatorModule中找到了方法定义:- (void)setNavBarRightItem:(NSDictionary *)param callback:(WXModuleCallback)callback。不过需要传一个字(对)典(象),没有文档只好翻源码了。源码比较简单这里就不解释了。因为界面一开始展示的时候就需要显示导航按钮,我们使用beforeCreate生命周期方法。


beforeCreate: function () {
navigator.setNavBarRightItem({
title: 'fun', // 编程很有乐趣
titleColor: 'blue' // 不设置就是透明的看不见
})
}

按钮可以显示,点击事件怎么解决呢?在网上搜了一下一无所获,还是啃代码自力更生吧。导航按钮的点击事件绑定到了WXNavigationDefaultImpl的- (void)onClickBarButton:(id)sender方法。最核心的一行代码,是触发了一个事件。


[[WXSDKManager bridgeMgr] fireEvent:button.instanceId ref:WX_SDK_ROOT_REF type:eventType params:nil domChanges:nil];

看到fireEvent方法有些懵,这里我解释一下前三个参数:


instanceId:页面的ID。避免给一个控制器的事件跑到另一个控制器。我猜测weex组件的ID只能保证在一个页面中唯一。ref:组件的ID,根据命名判断为根组件(实际情况也是如此)。eventType:事件类型,这里为clickrightitem。

既然事件给了根组件,我们只需要把点击事件绑定到根组件就可以啦。至于点击触发什么效果随意啦。


<template>
<div @clickrightitem="onclickrightitem">
...

最后效果是这样的。PS:导航按钮fun会在页面切换动画完毕才显示出来,暂时忍了吧。



这里我们提一下weex的“兄弟”React Native。在导航方面,它们的差异很大。

weex:基于UINavigationController,更贴近原生。缺陷是传参只能字符串,调试还需多终端。
React Native:基于主视图,切换动画由js管理。缺陷是用户体验不一致,埋点还需小心思。

各有千秋吧。

最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台