Weex为咱们提供了navigator模块来控制页面的导航。Navigator模块到底是怎么运做的,官方没有给咱们一个感性的认识。本文旨在探究weex的导航机制,而后实现一个DEMO供参考。javascript
推入一个页面,相似原生的pushViewController:animated:
`。文档告诉咱们这么作:vue
navigator.push({ url: 'http://dotwe.org/raw/dist/519962541fcf6acd911986357ad9c2ed.js', animated: "true" })
顺藤摸瓜,找到原生模块WXNavigatorModule
,push方法是这样定义的:java
- (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
的。web
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
来展现新的页面。咱们可能须要对导航进行定制,或者用一个咱们本身实现的控制器代替官方版本。只须要两步就能够作到:服务器
WXNavigationProtocol
协议类,替换官方版本。我实现了WXViewController
。这里偷个懒,直接继承官方的。我在新控制器中加入了自动刷新逻辑。你会好奇我为何不调用super
的viewDidLoad
方法?这是由于父类的实现中会隐藏导航栏(并且还有动画),我不想要这样的效果,也不明白这么设计的做用是什么。因而就经过子类覆盖了这个逻辑。weex
@interface WXViewController (Private) @property (nonatomic, strong) NSURL *sourceURL; - (void)_renderWithURL:(NSURL *)sourceURL; @end @interface WXViewController () <SRWebSocketDelegate> @property (nonatomic, strong) SRWebSocket *hotReloadSocket; @end @implementation 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到本身的项目就行啦。下面是个人实现,实际上只替换了容器控制器,其余代码不变。app
@interface WXNavigationImpl (Private) - (void)callback:(WXNavigationResultBlock)block code:(NSString *)code data:(NSDictionary *)reposonData; @end @implementation 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:dom
[WXSDKEngine registerHandler:[WXNavigationImpl new] withProtocol:@protocol(WXNavigationProtocol)];
设置根视图控制器,这里咱们使用了UINavigationController
保证导航可以被支持。ide
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 class="wrapper" @clickrightitem="onclickrightitem"> ...
最后效果是这样的。PS:导航按钮fun会在页面切换动画完毕才显示出来,暂时忍了吧。
这里咱们提一下weex的“兄弟”React Native。在导航方面,它们的差别很大。
UINavigationController
,更贴近原生。缺陷是传参只能字符串,调试还需多终端。各有千秋吧。