WKWebView 功能測試 (下) - 與 Javascript 交流

使用 Javascript 連結網頁與 APP

最後要測試的是最重要的功能,也就是使用 Javascript 連結網頁與 APP。

希望達到的功能有二種:

  • 由 APP 向網頁注入 Javascript,並取得傳回資料。

  • 網頁的 Javascript 執行 APP 的功能並傳資料給 APP。

有了這雙方溝通的功能,就可以做出很多應用了。

由 APP 向網頁注入 Javascript

在這裡我們要設計二個測試:

1. 把 APP 輸入欄位的內容寫到網頁中的 Label 中。

2. 可以執行網頁原本就有的 GetSel 函式,該函式就是把選取的文字寫在網頁上,並且傳回去。APP 取得後再填到輸入欄位上。

先準備網頁 index.htm

 

<!DOCTYPE html>

<html>

<head>

  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

  <title>Index</title>

  <link rel='stylesheet' type='text/css' href='mycss.css'>

  <script type="text/javascript" src="myjs.js"></script>

</head>

<body>

        <script>

        function GetSel()

        {

            str = window.getSelection().toString();

            ShowLabel(str);

            return str;

        }

        </script>

        <h1>Hello World!</h1>

        <div>Hi This <span style="color:red">is a test</span> html.</div>

        <div>My Label : <span id="MyLabel"></span></div>

        <a href="#" onclick="ShowLabel('This is a Label.')">func ShowLabel()</a><br>

        <a href="#" onclick="GetSel();">func GetSel()</a><br>

        <a href="index2.htm">Goto index2</a>

</body>

</html>

 

外部檔案的 myjs.js

function ShowLabel(str)

{

    document.getElementById("MyLabel").innerText = str;

}

向網頁注入 Javascript 是用 evaluateJavaScript 來實現的。 [官方文件]

在 WebView 上放置二個按鈕,一個輸入欄位。

第一個按鈕按下時,要將輸入欄位的文字填入網頁指定的位置。

 

@IBAction func btSendWebPageTextClick(_ sender: Any) {

    let strLabel = edEdit.stringValue

    let strJS = "document.getElementById('MyLabel').innerText = '\(strLabel)'"

    webView.evaluateJavaScript(strJS)

}

 

這個功能沒有傳回值,執行如果如下:

先在紅圈 1 的輸入欄輸入文字,接著按下紅圈 2 的按鈕,就會看到輸入的文字出現在網頁紅框 3 的位置。

image

另一個按鈕程式如下:

 

@IBAction func btGetWebPageTextClick(_ sender: Any) {

    webView.evaluateJavaScript("GetSel()",

        completionHandler: {

            (result, err) in

            self.edEdit.stringValue = result as! String

    })

}

 

這個程式是直接呼叫網頁中原有的 Javascript 函式 GetSel(),它的作用是把使用者選取的文字貼在網頁上 Lable 的位置,並且傳回文字。

程式中的 completionHandler 就是負責處理接收 Javascript 傳回的訊息,result 是傳回內容,err 是錯誤訊息。

上面沒有處理錯誤訊息,只是把傳回的 result 轉成字串,然後貼在 APP 的輸入欄位中。

執行結果如下。

先選取一段文字 (紅圈1),按下紅圈 2 的按鈕,執行 GetSel() 函式會把選取的文字貼在紅圈 3 的位置,這是網頁本身 Javascript 的功能。最後傳回該文字,由 APP 接受之後,呈現在紅圈 4 的輸入欄位中。

image

網頁的 Javascript 執行 APP 的功能

這個功能是指在 APP 中設定一個函式,而網頁中的 Javascript 可以呼叫這個函式,也可以利用這個函式把資料傳給 APP,等於是由網頁向 APP 傳送訊息。

這裡要再做一個測試,也就是在網頁中加個連結,如果點選網頁的連結,就會把選取的文字送給 APP。

而 APP 也會做一個接收的函式,若收到網頁傳來的文字,就把它呈現在輸入欄位中。

底下逐一說明實作:

我在某個網頁看到如下的作法:

 

// 先產生 WebViewConfiguration

let webConfiguration = WKWebViewConfiguration()

// 再產生它的 userContentController

webConfiguration.userContentController = WKUserContentController()

// 再指定 AppFunc 這個函式是可供 Javascript 呼叫的進入點

webConfiguration.userContentController.add(self, name: "AppFunc")

// 然後用 webConfig 來建立 webView

var webView = WKWebView(frame: .zero, configuration: webConfiguration)

 

不過我先前的程式是已經先產生 WebView 了,所以底下的方法是事後才把 WebViewConfiguration 加入 WebView 之中,測試之後也是可行的,程式如下:

 

// 取出 WebView 的 WebViewConfiguration

let webConfiguration = webView.configuration

// 將它加上 AppFunc 這個供 Javascript 呼叫的進人點

webConfiguration.userContentController.add(self, name: "AppFunc")

 

WebViewConfiguration 的 [官方文件]。

因為要接收網頁 Javascript 傳回的訊息,因此 ViewController 要繼承 WKScriptMessageHandler

 

class ViewController: NSViewController, WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler {

 

同時要加上這個特定函式來接受 Javascript 傳來的訊息,它要做的事只有一件,就是把傳回來的文字呈現在輸入欄位上。

 

func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {

    edEdit.stringValue = message.body as! String

}

 

 

message.body 是傳回的訊息,而 message.name 是進入點的名稱 "AppFunc"。如果加了好幾組進入點,就可以由 message.name 來判斷是哪一個函式傳回來的。

 

最後是 HTML 網頁中的呼叫方法,只要加上一個連結。

 

 

<a href="#" onclick="CallApp();">App Func()</a>

 

而 Javascript 如下,最重要的是要呼叫 

window.webkit.messageHandlers.AppFunc.postMessage()

那個 AppFunc 就是前面設計的進入點名稱。這個程式就是把使用者選取的文字傳給 APP。

 

function CallApp()

{

  str = window.getSelection().toString();

  window.webkit.messageHandlers.AppFunc.postMessage(str);

}

 

執行如果如下。

使用者先選取文字 (紅圈 1),然後按下網頁中紅圈 2 的 App Func() 連結,就會執行 CallApp(),也就是把選取的文字傳給 APP,APP 再把它呈現在紅圈 3 的位置。

image

至此,我們就可以讓 APP 與網頁互相溝通無礙了。

最後呈現完整網頁與程式。

index.htm

 

<!DOCTYPE html>

<html>

<head>

  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

  <title>Index</title>

  <link rel='stylesheet' type='text/css' href='mycss.css'>

  <script type="text/javascript" src="myjs.js"></script>

</head>

<body>

    <script>

    function GetSel()

    {

        str = window.getSelection().toString();

        ShowLabel(str);

        return str;

    }

    function CallApp()

    {

        str = window.getSelection().toString();

            window.webkit.messageHandlers.AppFunc.postMessage(str);

    }

    </script>

    <h1>Hello World!</h1>

    <div>Hi This <span style="color:red">is a test</span> html.</div>

    <div>My Label : <span id="MyLabel"></span></div>

    <a href="#" onclick="ShowLabel('This is a Label.')">func ShowLabel()</a><br>

    <a href="#" onclick="GetSel();">func GetSel()</a><br>

    <a href="index2.htm">Goto index2</a><br><br>

    <a href="#" onclick="CallApp();">App Func()</a>

</body>

</html>

 

程式碼

 

import Cocoa

import WebKit

 

class ViewController: NSViewController, WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler {

    

    // MARK: 自訂屬性與元件

    

    let webView = WKWebView()

    @IBOutlet weak var edEdit: NSTextField!

    

    // MARK: 預設成員函式

    

    override func viewDidLoad() {

        super.viewDidLoad()

        // Do any additional setup after loading the view.

        

        setupWebview()      // 設定 webView

        self.view.addSubview(webView)

        webviewConstraint() // 約束 webView

        loadWebview()       // 載入網頁

    }

 

    override var representedObject: Any? {

        didSet {

        // Update the view, if already loaded.

        }

    }

    

    // MARK: 自訂成員函式

    // 設定 webView

    fileprivate func setupWebview() {

        self.webView.uiDelegate = self

        self.webView.navigationDelegate = self

        let webConfiguration = webView.configuration

        webConfiguration.userContentController.add(self, name: "AppFunc")

    }

    

    // 接受網頁傳回的資訊

    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {

        edEdit.stringValue = message.body as! String

    }

    

    // 約束 webView

    fileprivate func webviewConstraint() {

        webView.translatesAutoresizingMaskIntoConstraints = false

        

        let ctLeft = NSLayoutConstraint(item: webView, attribute: .leading, relatedBy: .equal, toItem: self.view, attribute: .leading, multiplier: 1, constant: 20)

        let ctRight = NSLayoutConstraint(item: webView, attribute: .trailing, relatedBy: .equal, toItem: self.view, attribute: .trailing, multiplier: 1, constant: -20)

        let ctTop = NSLayoutConstraint(item: webView, attribute: .top, relatedBy: .equal, toItem: self.view, attribute: .top, multiplier: 1, constant: 50)

        let ctBottom = NSLayoutConstraint(item: webView, attribute: .bottom, relatedBy: .equal, toItem: self.view, attribute: .bottom, multiplier: 1, constant: -20)

        

        NSLayoutConstraint.activate([ctLeft,ctRight,ctTop,ctBottom])

    }

    

    // 載入網頁

    fileprivate func loadWebview() {

        webView.loadFileURL(URL(string: "file:///Users/heaven/desktop/html/index.htm")!, allowingReadAccessTo: URL(string: "file:///Users/heaven/desktop/html")!)

    }

    

    // MARK: 元件綁定的事件

    

    @IBAction func btSendWebPageTextClick(_ sender: Any) {

        let strLabel = edEdit.stringValue

        let strJS =

        "document.getElementById('MyLabel').innerText = '\(strLabel)'"

        webView.evaluateJavaScript(strJS)

    }

    

    @IBAction func btGetWebPageTextClick(_ sender: Any) {

        webView.evaluateJavaScript("GetSel()",

            completionHandler: {

            (result, err) in

            self.edEdit.stringValue = result as! String

        })

    }

}

 

 
重要度:
文章分類:

發表新回應

借我放一下廣告