WKWebView 功能測試 (上) - 建置、設定、呈現

 

以前 Mac 程式內建的瀏覽器元件是用 WebView,iOS 則是用 UIWebView,而現在最新的是用 WKWebView,不過文中若提到 WebView 通常就是指 WKWebView,除非有特別強調是指舊版的框架。

建置 WKWebView

因為直接使用 WebView 元件似乎會有問題,在 Xcode 上雖然可以執行,但是直接由 APP 執行就看不到 WebView,所以採用使用 code 來建置 WebView。

底下的 code 是 copy 來的,黑色是原來就有的,紅色是後來加上去的。

 

import Cocoa

import WebKit

 

class ViewController: NSViewController, WKUIDelegate, WKNavigationDelegate {

 

    let webView = WKWebView()

    

    override func viewDidLoad() {

        super.viewDidLoad()

 

        // Do any additional setup after loading the view.

        

        self.webView.uiDelegate = self

        self.webView.navigationDelegate = self

        webView.frame = CGRect(x:0,y:0,width:400,height: 270)

        view.addSubview(webView)

                

        let myURL=URL(string: "https://www.apple.com")

        let myRequest = URLRequest(url: myURL!)

        webView.load(myRequest)

    }

 

    override var representedObject: Any? {

        didSet {

        // Update the view, if already loaded.

        }

    }

}

 

只有建置 WebView 是無法連線的,還要開啟網路的功能才行。

如下圖,選擇專案 -> TARGETS -> Signing... -> App Sandbox

開啟 Netword 的 Outgoing Connections (Client)

image

設定之後再次執行,就可以看到成果了。

image

設定 WebView 位置

接下來要設計 WebView 的位置,因為它不是在 Storyboard 處理的,無法在 Storyboard 上使用工具調整,需要用程式碼來處理。

我希望它是幾乎滿框,但上面要留一小段空間放置一些元件。

在 MS Window 寫程式時,我大概就是先放一個 Panel,讓它高度固定並置頂,再放 WebView 填滿整個 Client 就可以了。

在 Mac 中,目前知道的是採用自動布局的「約束」(Constraint),也就是某個元件某個值要和另一個元件某個值保持某種關係。[官方文件]

例如我希望 WebView 的左邊要和視窗的左邊保持 20px。

在 MS Window 程式,就是在 Windows 的 OnResize 視事件中撰寫

webview->left = 20 (這是相對主視窗的)

Mac 的做法就是建立一個約束:

 

let ctLeft = NSLayoutConstraint(

item: webView, // webView 這個元件

attribute: .leading, // 的左邊

relatedBy: .equal, // 要等於

toItem: self.view, // 主視窗

attribute: .leading, // 的左邊

multiplier: 1, // x 1

constant: 20) // 再加 20

 

.leading 是左邊,.trailing 是右邊。上面紅色的註解就是各屬性的說明。用程式碼來描述大概就是

webView.left = (self.view.left * 1) + 20

這張是 [官方文件] 裡面的圖,也可以參考它的邏輯。

image

程式碼看起來雖然有點囉嗦,但在 Xcode 上函式參數都自動填好了,只要填進去我們要的資料,也算是方便。

就依上面的方法,建立好四邊的約束之後,再執行

 

self.view.addConstraint(ctLeft)

self.view.addConstraint(ctRight)

self.view.addConstraint(ctTop)

self.view.addConstraint(ctBottom)

 

就把四條約束加進去了。

也可以採用這個方式來處理:

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

以上完成後,要記得要加上底下這行,理由後面會談到。

webView.translatesAutoresizingMaskIntoConstraints = false

這是完成的原始碼,因為有約束,所以初始大小就不重要了,所以可以設定 webView.frame = .zero

 

webView.frame = .zero

self.view.addSubview(webView)

 

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)

        

self.view.addConstraint(ctLeft)

self.view.addConstraint(ctRight)

self.view.addConstraint(ctTop)

self.view.addConstraint(ctBottom)    

 

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

 

這是完成的畫面,上面空間較大,我放了二個元件,左、右、下各 20 點的距離。

image

約束還有不少做法,例如我希望 WebView 的寬度等於主視窗的寬度,就不用設定左右邊界,直接設定寬度,約束就可以少一項,如下:

 

let ctWidth = NSLayoutConstraint(item: webView, attribute: .width, relatedBy: .equal, toItem: self.view, attribute: .width, multiplier: 1, constant: 0)

        

self.view.addConstraint(ctWidth)   

self.view.addConstraint(ctTop)

self.view.addConstraint(ctBottom)      

 

// NSLayoutConstraint.activate([ctWidth, ctTop, ctBottom])

 

這裡要說明一些注意事項:

在沒有任何約束時,主畫面若拉大,WebView 都是固定在左下角,因為它是以左下角為原點座標,並且 WebView 有固定大小。

一開始測試時,我只設了右邊的約束,原本以為如此一來,左右都會保持固定的距離,結果卻變成視主窗無法調整寬度。

原來是因為 WebView 已經有座標,也有長和寬了,再加上右邊又與主視窗有約束,主視窗就不能拉了,否則不是約束失效,就是 WebView 寬度改變,這都是不符合原先的要求。

所以 Google 了一下,才知道要加上這一行:

webView.translatesAutoresizingMaskIntoConstraints = false

問題又來了,當我加入這一行之後,執行畫面就看不到 WebView 了。

又做了不少測試,到處翻資料,才知道約束的設計要齊全,因為已經加了那一行,表示目前要採用約束布局,先前規畫的布局已經失效了,若此時約束不夠齊全,有些資訊不足,就無法呈現合理的畫面。

所以要乖乖的將所有的約束都設計好,果然這樣就能正確呈現了。

載入文件

我在 Mac 上要寫的程式主要是由 WebView 載入本地文件,所以我把載入網頁的程式改成載入文件。

let myURL=URL(string: "https://www.apple.com")

改成

let myURL=URL(string: "file:///Users/heaven/desktop/index.htm")

結果打開一看,一片空白。

image

Xcode 有這樣的訊息,看起來是因為文件在沙箱 (sandbox) 之外,因此不允許讀取。

image

我查了一些資料,也還沒搞定如何處理,所以我依某些網頁的建議把設定中的沙箱刪除了,也就是點選底下紅框的位置。

image

然後再執行,系統就跳出詢問視窗。

image

選擇同意後,就可以看到底下的測試畫面了。

image

以上畫面有外部 css、js,以及內部的 css 和 js,還有連結,目前測試功能皆正常。

載入檔案系統的網頁還可以用 loadFileURL 的功能,如下:

 

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

 

以上我加入一層 html 目錄。

經測試結果,若把外部的 css 和 js 放在 destop 目錄中,而 allowingReadAccessTo 卻是 desktop/html 就會無法載入,因為 css 和 js 在允許的目錄之外,除非把 allowingReadAccessTo 設定至 desktop 就沒有問題了。

連結非安全性的 http 網頁

在實測中,發現 http 開頭的非安全網頁無法連結,網路上查到二種類似的方法,都是要修改 info.plist 文件。

 

修改法有二種,一種是在 Xcode 中改,如下圖,底下那二種都有人提到,實際測試也都可以連結 http 網頁。顧名思義,第一種 "All Arbitrary Loads in Web Content" 似乎稍為安全一點。

 

image

 

 

另一種就是直接用純文字編輯器修改 info.plist,修改法就是加入這一段,紅色或藍色二選一即可。

 

<key>NSAppTransportSecurity</key>

<dict>

  <key>NSAllowsArbitraryLoads</key>

  <true/>

  <key>NSAllowsArbitraryLoadsInWebContent</key>

  <true/>

</dict>

image

連結 target = "_blank" 的網頁

在實測中還發現一個問題,若網頁連結是 target="_blank",在一般瀏覽器就是開啟新頁面,但在 WKWebView 就沒有反應,查到要加入這段程式,就可以順利開啟網頁了。

 

 

func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {

 

    // 若 target="_blank",targetFrame 會 = nil

    if navigationAction.targetFrame == nil {

        // 傳入 .cancel 停止 navigation 預設的行為

        decisionHandler(.cancel)

        webView.load(navigationAction.request)

        return

    }

    decisionHandler(.allow)

}

 

 

重要度:
文章分類:

發表新回應

借我放一下廣告