如果有發現程式碼有問題歡迎留言告訴我!
前言
因為點了 WKWebView 中網頁的超連結發現他沒有反應,才發現這需要另外處理。
環境
Xcode 11.3, Swift 5, Storyboard
一些些前導知識
普通的超連結不需要手動處理, WKWebView 就會自己幫我們導到下一個頁面了。
target="_blank"
是 HTML 裡面 a (錨點, anchor) 標籤中,用來指定這個超連結應該要開一個新的視窗。
如果不加這個屬性,如下,就會在同一個頁面導向下個頁面。
<a href="https://apple.com">Apple</a>
如果加了該屬性,原先的頁面就不會有任何動作,在一般的瀏覽器就會幫你開一個新的視窗。
<a href="https://apple.com" target="_blank">Apple</a>
參照
_blank
Opens the linked document in a new window or tab
範例
在 Xcode 開一個新專案來試試看,在 Storyboard 裡面放一個 WKWebView 進去,並把他設定 IBOutlet 到所屬的 view controller 。
這裡就略過 Storyboard 裡面放了什麼,直接放初始化的程式碼。
import UIKit
import WebKit
class ViewController: UIViewController {
@IBOutlet weak var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
webView.navigationDelegate = self
webView.loadHTMLString(htmlString, baseURL: nil)
}
}
extension ViewController: WKNavigationDelegate {
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
// 在這裡先讓他執行預設的行為
decisionHandler(.allow)
}
}
extension ViewController {
/// 實際內容
var htmlString: String {
return """
<h1><a href="https://apple.com" target="_blank">Apple</a></h1>
<h1><a href="https://apple.com">Apple</a></h1>
"""
}
}
實際的內容很簡單,就是兩個 Apple 的超連結,一個是有指定要新開頁面,另一個則是直接在原先的頁面跳轉。放 h1 的原因是 web view 中預設的字級會超小,直接用 h1 讓他成為大標變大字。
<h1><a href="https://apple.com" target="_blank">Apple</a></h1>
<h1><a href="https://apple.com">Apple</a></h1>
在 WKWebView 中的行為
到這裡先執行看看,
就會發現第一個超連結點下去之後他不會進行任何的跳轉, console 中還會出現像是這樣的錯誤訊息。
這個就是我們在這邊要解決的問題。
Unknown result for URL 0x2832bc0e0 (https)
而第二個超連結點下去則會順利的導向 Apple 的首頁。
得知按了一個連結和操作
在上面初始化的程式碼裡面也有寫出來,WKNavigationDelegate 中,有方法可以監聽,並作出對應的處理。在這邊就是使用這個方法。
optional func webView(_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void)
在這個方法裡面,可以對 decisionHandler 傳入不同的值來告訴他該不該繼續跳轉或者是說進行預設的行為。 .allow
則是預設的行為。
decisionHandler(.allow)
取得這個超連結的內容並處理
我們可以透過第二個參數來取得超連結的內容,而在這邊我們想要看的就是和 target
屬性有相關的參數,也就是 targetFrame
navigationAction.targetFrame
如文件裡面的敘述:
The target frame, or nil if this is a new window navigation.
就可以知道,如果是超連結本身指定要開新視窗的話,這個屬性的值就會是 nil 。
檢查 targetFrame
因此這個方法的內容就可以改成這樣:
extension ViewController: WKNavigationDelegate {
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
// 為求範例單純化這邊只檢查 targetFrame ,
// 實務上還是需要加上檢查 url 是不是合法可以開啟等等處理
if navigationAction.targetFrame == nil {
// 傳入 .cancel 停止 navigation 預設的行為
decisionHandler(.cancel)
// 這裡就是開始做想做的事情, webView.load 可以直接讓當前的 webView 導向目標頁面
// 當然也可以開啟 SFSafariViewController 或是開啟 Mobile Safari / Mobile Chrome
webView.load(navigationAction.request)
// 呼叫過之後 devisionHandler 之後就在這邊讓這個方法結束
return
}
decisionHandler(.allow)
}
}
注意點
decisionHandler 呼叫第二次(及以上)會 crash
最後的程式碼
import UIKit
import WebKit
class ViewController: UIViewController {
@IBOutlet weak var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
webView.navigationDelegate = self
webView.loadHTMLString(htmlString, baseURL: nil)
}
}
extension ViewController: WKNavigationDelegate {
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
if navigationAction.targetFrame == nil {
decisionHandler(.cancel)
webView.load(navigationAction.request)
return
}
decisionHandler(.allow)
}
}
extension ViewController {
var htmlString: String {
return """
<h1><a href="https://apple.com" target="_blank">Apple</a></h1>
<h1><a href="https://apple.com">Apple</a></h1>
"""
}
}
參考
- 官方文件
-
https://stackoverflow.com/a/40742587
- 有提到處理
target="_blank"
- 有提到處理
-
https://stackoverflow.com/a/39724007
- 有提到怎麼處理超連結