📚
WKWebViewについてのまとめ(SwiftUIでの使い方も)
WKWebViewのサンプルを作成したのでメモを残しておきます。
2019/11/16: WKWebView内の画面遷移のフックやjsとの連携について加筆・修正しました
2021/01/02: SwiftUIでWKWebViewを使う方法を追記しました
開発環境
- Xcode:11.2.1
- Swift:5.1.2
実装
WKWebViewのセットアップ
Setup
// MARK: - Setup WKWebView
private func setupWKWebView() {
let webConfig = WKWebViewConfiguration()
wkWebView = WKWebView(frame: .zero, configuration: webConfig)
wkWebView.navigationDelegate = self // Delegate①:画面の読み込み・遷移系
wkWebView.uiDelegate = self // Delegate②:jsとの連携系
view = wkWebView
}
Webページのロード
Load
// MARK: - Load Web Page
private func load(withURL urlStr:String) {
guard let url = URL(string: urlStr) else { return }
let request = URLRequest(url: url)
wkWebView.load(request)
}
Delegateメソッド
Delegate①:画面の読み込み・遷移系
WKNavigationDelegate
// MARK: - 読み込み設定(リクエスト前)
func webView(_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
print("リクエスト前")
/*
* WebView内の特定のリンクをタップした時の処理などが書ける(2019/11/16追記)
*/
let url = navigationAction.request.url
print("読み込もうとしているページのURLが取得できる: ", url ?? "")
// リンクをタップしてページを読み込む前に呼ばれるので、例えば、urlをチェックして
// ①AppStoreのリンクだったらストアに飛ばす
// ②Deeplinkだったらアプリに戻る
// みたいなことができる
/* これを設定しないとアプリがクラッシュする
* .allow : 読み込み許可
* .cancel : 読み込みキャンセル
*/
decisionHandler(.allow)
}
// MARK: - 読み込み準備開始
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
print("読み込み準備開始")
}
// MARK: - 読み込み設定(レスポンス取得後)
func webView(_ webView: WKWebView,
decidePolicyFor navigationResponse: WKNavigationResponse,
decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
print("レスポンス取得後")
/* これを設定しないとアプリがクラッシュする
* .allow : 読み込み許可
* .cancel : 読み込みキャンセル
*/
decisionHandler(.allow)
// 注意:受け取るレスポンスはページを読み込みタイミングのみで、Webページでの操作後の値などは受け取れない
}
// MARK: - 読み込み開始
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
print("読み込み開始")
}
// MARK: - ユーザ認証(このメソッドを呼ばないと認証してくれない)
func webView(_ webView: WKWebView,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
print("ユーザ認証")
completionHandler(.useCredential, nil)
}
// MARK: - 読み込み完了
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
print("読み込み完了")
}
// MARK: - 読み込み失敗検知
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError: Error) {
print("読み込み失敗検知")
}
// MARK: - 読み込み失敗
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError: Error) {
print("読み込み失敗")
}
// MARK: - リダイレクト
func webView(_ webView: WKWebView, didReceiveServerRedirectForProvisionalNavigation:WKNavigation!) {
print("リダイレクト")
}
Delegate②:jsとの連携系(2019/11/16追記)
WKUIDelegate
// alertを表示する
func webView(_ webView: WKWebView,
runJavaScriptAlertPanelWithMessage message: String,
initiatedByFrame frame: WKFrameInfo,
completionHandler: @escaping () -> Void) {
let alertController = UIAlertController(title: "title",
message: "message",
preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default) { action in
completionHandler()
}
alertController.addAction(okAction)
present(alertController ,animated: true ,completion: nil)
}
// confirm dialogを表示する
func webView(_ webView: WKWebView,
runJavaScriptConfirmPanelWithMessage message: String,
initiatedByFrame frame: WKFrameInfo,
completionHandler: @escaping (Bool) -> Void) {
let alertController = UIAlertController(title: "title",
message: "message",
preferredStyle: .alert)
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { action in
completionHandler(false)
}
let okAction = UIAlertAction(title: "OK", style: .default) { action in
completionHandler(true)
}
alertController.addAction(cancelAction)
alertController.addAction(okAction)
present(alertController ,animated: true ,completion: nil)
}
// 入力フォーム(prompt)を表示する
func webView(_ webView: WKWebView,
runJavaScriptTextInputPanelWithPrompt prompt: String,
defaultText: String?,
initiatedByFrame frame: WKFrameInfo,
completionHandler: @escaping (String?) -> Void) {
let alertController = UIAlertController(title: "title",
message: prompt,
preferredStyle: .alert)
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { action in
completionHandler("")
}
let okHandler = { () -> Void in
if let textField = alertController.textFields?.first {
completionHandler(textField.text)
} else {
completionHandler("")
}
}
let okAction = UIAlertAction(title: "OK", style: .default) { action in
okHandler()
}
alertController.addTextField() { $0.text = defaultText }
alertController.addAction(cancelAction)
alertController.addAction(okAction)
present(alertController ,animated: true ,completion: nil)
}
イベントを検知して、アラートやダイアログはネイティブ側で作って表示するイメージ。
他にJsと連携してできること(2019/11/16追記)
①js側から処理を受け取ってネイティブ側の処理を実行する
②ネイティブ側からjsのfunctionを叩く・DOMをいじる
これに関しては次回更新の時に記載できれば良いなと思います。
#実行結果(画面の読み込み・遷移系)
実行結果
リクエスト前
読み込み準備開始
レスポンス取得後
読み込み開始
ユーザ認証
ユーザ認証
読み込み完了
想定通りに動いてくれて良かった(認証が2回通ってるけど...)
SwiftUIでWKWebViewを使う
結論から言うと、現状では UIViewRepresentable
を継承して自作するしかなさそうです。(2020/01/02時点)
UIViewRepresentableとは
SwiftUIにてUIKitのViewを使用するためのラッパーです。
UIKitのViewをSwiftUIで使用するにはUIViewRepresentableを使用する必要があります。
MyWebView
import SwiftUI
import WebKit
struct MyWebView: UIViewRepresentable {
let url: String
private let observable = WebViewURLObservable()
/// 監視する対象を指定して値の変化を検知する
var observer: NSKeyValueObservation? {
observable.instance
}
// MARK: - UIViewRepresentable
/// 表示するViewのインスタンスを生成
/// SwiftUIで使用するUIKitのViewを返す
func makeUIView(context: Context) -> WKWebView {
WKWebView()
}
// MARK: - UIViewRepresentable
/// アプリの状態が更新される場合に呼ばる
/// Viewの更新処理はこのメソッドに記述する
func updateUIView(_ uiView: WKWebView, context: Context) {
/// WKWebViewのURLが変わったこと(WebView内画面遷移)を検知して、URLをログ出力する
observable.instance = uiView.observe(\WKWebView.url, options: .new) { view, change in
if let url = view.url {
print("Page URL: \(url)")
}
}
/// URLを指定してWebページを読み込み
uiView.load(URLRequest(url: URL(string: url)!))
}
}
// MARK: WKWebViewのURLが変わったこと(WebView内画面遷移)を検知するための `ObservableObject`
private class WebViewURLObservable: ObservableObject {
@Published var instance: NSKeyValueObservation?
}
使い方
ContentView
import SwiftUI
struct ContentView: View {
var body: some View {
MyWebView(url: "https://www.apple.com/")
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
まとめ
まだまだ深く突っ込めると思うのでもう少し触ってみようと思いますー
ご指摘などいただけると嬉しいです。
Discussion