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
,更貼近原生。缺陷是傳參只能字符串,調試還需多終端。各有千秋吧。