Weex中頁面導航的實現

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來展現新的頁面。咱們可能須要對導航進行定製,或者用一個咱們本身實現的控制器代替官方版本。只須要兩步就能夠作到:服務器

  • 實現本身的weex容器控制器。
  • 實現本身的WXNavigationProtocol協議類,替換官方版本。

我實現了WXViewController。這裏偷個懶,直接繼承官方的。我在新控制器中加入了自動刷新邏輯。你會好奇我爲何不調用superviewDidLoad方法?這是由於父類的實現中會隱藏導航欄(並且還有動畫),我不想要這樣的效果,也不明白這麼設計的做用是什麼。因而就經過子類覆蓋了這個邏輯。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。在導航方面,它們的差別很大。

  • weex:基於UINavigationController,更貼近原生。缺陷是傳參只能字符串,調試還需多終端。
  • React Native:基於主視圖,切換動畫由js管理。缺陷是用戶體驗不一致,埋點還需當心思。

各有千秋吧。

相關文章
相關標籤/搜索