【重要】專為你定製的 JS/Natvie 互動專題

知識小集發表於2018-07-17

2018 年距離第一代 iOS 系統釋出(2007 年)已經過去 11 年,這 11 年中移動端日益成熟,Web 端的時代逐步轉移到了移動端,自然而然 Web 端的開發技術棧開始逐步移動到移動端。這就引發一個尷尬的局面,Web 端的同學不瞭解移動端的開發知識,移動端不瞭解 Web 端的開發知識。為了解決這個問題,知識小集打算從基礎出發,介紹 JavaScript 與 iOS 互動時用到的技術點,比如 JavaScriptCore、JavaScript 基礎、JavaScriptCore 的實際使用場景(深度剖析 JSPatch 的實現)等。而今天這篇就是其中的一篇,主要介紹一個 Hybrid WebView 的實現。接下來我們會把文章逐步發出來供我們的讀者朋友參考,我們初步定的目錄如下(如果你不想錯過我們這個專題,關注我們的公眾號【知識小集】吧,關於這個專題有什麼建議都可以通過公眾號告訴我們):

  • 前言
  • JavaScript 基礎知識
  • JavaScript 進階
  • JavaScript-native 除錯
  • 開啟本地 Webserver
  • WKWebView 概述
  • JavaScriptCore 總覽
  • JavaScript 與 ObjectiveC 間的型別轉換
  • JavaScript 與 ObjectiveC 通訊
  • ObjectiveC 與 JavaScript 通訊
  • 自己動手實現一個 Hybrid WebView
  • JSPatch 中的 JavaScriptCore
  • JSPatch 中的 Runtime
  • JSPatch 原理深度剖析
  • JSPatch 雜談
  • 讀 Aspects 理解 runtime

自己動手實現一個 Hybrid WebView

如今,端與 Web 頁的互動越來越頻繁,很多頁面都交給 Web 頁面來實現,而有些情況下 Web 需要與端進行互動。面對這種需求,各種第三方庫源源不斷出現,而 WebViewJavascriptBridge 無疑是 star 最多的一個。其實目前在 iOS 開發當中,大多數都切換到了 WKWebView,且對 Web 的互動越來越重,所以不妨自己實現一個 Hybrid WebView 來滿足自己的業務需求。一個 Hybrid WebView 最基本的應該滿足雙方可以自由通訊。

  • WebView 上的事件可以傳遞到端上;
  • WebView 可以從端上獲取資料;
  • 端可以監聽到 WebView 上發生的事件。

本文旨在說明一個 Hybrid WebView 需要的技術手段,所以打算從一個具體的需求出發,一步一步搭建一個 Hybrid WebView。大多數的文章只會講解端上如何實現,而本文會結合前端一塊講講兩端是如何實現的。

需求說明

Web 頁面上有一張圖和一個儲存按鈕,當點選儲存按鈕時會提示使用者是否需要儲存圖片到相簿。如果儲存成功,按鈕的標題將變為已儲存,否則標題為儲存到相簿。如果已儲存,下次進入 Web 頁時顯示已儲存。

分析上面的需求,可以拆分為:

  • 頁面載入後,需要獲取圖片是否已經儲存過,如果已儲存,按鈕的標題為“已儲存”,否則為“儲存到相簿”;
  • 點選按鈕需要提示使用者“是否需要儲存圖片到相簿”,點選“儲存”執行儲存操作。點選取消將什麼也不做;
  • 儲存成功,按鈕上的標題需要變為“已儲存”。

分析完上面具體需求後,轉換為技術需要考慮的問題:

  • 頁面載入後,Web 頁可以從端上獲取到圖片是否已經儲存的狀態;
  • 點選儲存按鈕,需要在端上提示使用者,使用者點選儲存需要把圖片儲存到相簿,這時需要獲取到當前顯示的圖片,也就是說需要把 Web 頁面中的資料傳遞到端;
  • 儲存成功後需要修改 Web 頁面按鈕的標題。

先做一個 Web 頁面

【重要】專為你定製的 JS/Natvie 互動專題

整體頁面是如上圖所示。我們逐步剖析是如何實現的。

在前面的章節中(這些章節後續會發出來),已經介紹了在 Web 頁面中執行 JavaScript 。可以把一段 JavaScript 程式碼嵌入到 HTML 中,這時在 HTML 中可以直接呼叫 JavaScript 程式碼,而 JavaScript 可以通過 DOM 動態來操作 HTML 中的標籤,這樣既可以達到動態修改 Web 頁。

Web與端通訊的JS程式碼,這段程式碼是嵌入在 HTML 中的。

<script>
    // 標記儲存的狀態
    var saved = false;
    // 儲存事件
    function saveaction(){
        if (saved) {
            return;
        }
        alert("確定要儲存該圖片嗎?");
        // 傳送訊息給客戶端 JS 中傳送訊息給 OC
        var param =  {url : "https://raw.githubusercontent.com/iOS-Tips/iOS-tech-set/master/images/qrcode.jpg"};
        window.webkit.messageHandlers.JSBridge.postMessage(JSON.stringify(param));
    };
    // 儲存成功後端會呼叫這個方法通知Web頁儲存成功
    function save_success(){
        change_state(true);
    };
    // 修改是否已儲存的狀態,修改按鈕標題
    function change_state(issaved){
        saved = issaved;
        var button = document.getElementById('saveid');
        if (issaved){
            // 如果已經儲存,修改按鈕的標題為已儲存,否則顯示 儲存到相簿
            button.innerText = "已儲存";
        } else {
            button.innerText = "儲存到相簿";
        }
    }
</script>
複製程式碼

儲存到相簿 按鈕,監聽點選事件,當點選按鈕後會呼叫 saveaction 函式。

<div id="saveid" class="save_button" onclick="saveaction()">儲存到相簿</div>
複製程式碼

saveaction 函式首先會發一個 alert("確定要儲存該圖片嗎?") 到端,端會執行 WKUIDelegate 代理方法,我們在這個方法需要彈窗端內的提示框:

- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler
{
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"溫馨提示" message:message preferredStyle:UIAlertControllerStyleAlert];
    [alert addAction:[UIAlertAction actionWithTitle:@"儲存" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        self.isOKAction = YES;
        completionHandler();
    }]];
    [alert addAction:[UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        self.isOKAction = NO;
        completionHandler();
    }]];
    [self presentViewController:alert animated:YES completion:nil];
}
複製程式碼

當使用者點選儲存按鈕後,會儲存圖片到相簿。所以客戶端需要拿到圖片的地址,這是需要給端傳送圖片的地址。如果想給端傳送一條訊息,直接在 Web 頁通過 JavaScript 執行,其中 xxxx 是端與Web之間約定的名字。

window.webkit.messageHandlers.xxxx.postMessage(JSON.stringify(param))
複製程式碼

而我們此時定義的名字是 JSBridge,當使用者點選儲存後,需要根據Web傳遞過來的 URL 儲存圖片。

var param =  {url : "https://raw.githubusercontent.com/iOS-Tips/iOS-tech-set/master/images/qrcode.jpg"};
window.webkit.messageHandlers.JSBridge.postMessage(JSON.stringify(param));
複製程式碼

當端接收到 Web 發過來的訊息後,會呼叫 WKScriptMessageHandler 的代理方法,在這個方法中我們來下載圖片並儲存到相簿:

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
    if ([message.body isKindOfClass:[NSString class]]) {
        if ([message.name isEqualToString:kScriptMsgName] && self.isOKAction) {
            // 儲存圖片
            NSDictionary *msgInfo = [NSJSONSerialization JSONObjectWithData:[message.body dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingAllowFragments error:nil];
            UIImage *image = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:msgInfo[@"url"]]]];
            if (image) {
                UIImageWriteToSavedPhotosAlbum(image, self, @selector(imageSavedToPhotosAlbum:didFinishSavingWithError:contextInfo:), nil);
            }
        }
    }
}
複製程式碼

當把圖片儲存到相簿後,需要重新整理 Web 頁面上的按鈕的標題,這時需要執行 Web 頁中已經定義好的 change_state 方法:

- (void)updateSaveState:(BOOL)isSave
{
    NSString *script = isSave ? @"change_state(true);" : @"change_state(false);";
    [self.webView evaluateJavaScript:script completionHandler:^(id _Nullable msg, NSError * _Nullable error) {}];
}

複製程式碼

至此,我們還剩下最後一件事沒有完成,當載入出 WebView 後,需要根據本地是否已經儲存了圖片更新按鈕的標題,直接呼叫 updateSaveState 函式即可。

總結

本文主要介紹一個 Hybrid WebView 如何實現,它僅僅是從一個具體的需求出發,而如果做一個通用 Hybrid WebView 框架需要兩端設計一種通訊規則。具體細節可以參考味精的兩篇關於 Hybrid 的實踐 (從零收拾一個hybrid框架)。本文的 demo 會在這個專題完成後一塊放出。

【重要】專為你定製的 JS/Natvie 互動專題

相關文章