- WKWebView 功能測試 (上) - 建置、設定、呈現
- WKWebView 功能測試 (下) - 與 Javascript 交流
以前 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)
設定之後再次執行,就可以看到成果了。
設定 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
這張是 [官方文件] 裡面的圖,也可以參考它的邏輯。
程式碼看起來雖然有點囉嗦,但在 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 點的距離。
約束還有不少做法,例如我希望 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")
結果打開一看,一片空白。
Xcode 有這樣的訊息,看起來是因為文件在沙箱 (sandbox) 之外,因此不允許讀取。
我查了一些資料,也還沒搞定如何處理,所以我依某些網頁的建議把設定中的沙箱刪除了,也就是點選底下紅框的位置。
然後再執行,系統就跳出詢問視窗。
選擇同意後,就可以看到底下的測試畫面了。
以上畫面有外部 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" 似乎稍為安全一點。
另一種就是直接用純文字編輯器修改 info.plist,修改法就是加入這一段,紅色或藍色二選一即可。
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>NSAllowsArbitraryLoadsInWebContent</key>
<true/>
</dict>
連結 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)
}
- WKWebView 功能測試 (上) - 建置、設定、呈現
- WKWebView 功能測試 (下) - 與 Javascript 交流
- 瀏覽次數:14247
發表新回應