見出し画像

iOSアプリ開発 入門 (3) -WKWebView

iOSアプリの「WKWebView」による「Webビュー」の実装方法をまとめました。

・iOS 14

前回

1. WKWebView

iOSアプリでカスタマイズ可能な「Webビュー」を実装するには、「WKWebView」を使います。

2. Info.plist

「http通信」(https通信ではなく)を行うには、以下のタグをInfo.plistに追加する必要があります。

<key>NSAppTransportSecurity</key>
<dict>
   <key>NSAllowsArbitraryLoads</key>
   <true/>
   <key>NSAllowsArbitraryLoadsForMedia</key>
   <true/>
   <key>NSAllowsArbitraryLoadsInWebContent</key>
   <true/>
</dict>

画像2

3. HTMLの表示

HTMLを表示するコードは、次のとおりです。

import UIKit
import WebKit

// ViewController
class ViewController: UIViewController {
    // UI
    var webView: WKWebView!
   
    // ビューのロード時に呼ばれる
    override func viewDidLoad() {
        super.viewDidLoad()
      
        // コンフィギュレーションの準備
        let config = WKWebViewConfiguration()
        
        // Webビューの生成
        self.webView = WKWebView(frame:.zero, configuration: config)
        self.view = self.webView
       
        // HTMLの表示
        self.load("https://www.google.com/")
    }
  
    // HTMLの表示
    private func load(_ url: String) {
        self.webView.load(URLRequest(url: URL(string: url)!))
    }
}

4. ローカルHTMLの表示

ローカルHTMLを表示する手順は、次のとおりです。

(1) ローカルHTMLの準備。
htmlフォルダにindex.htmlを配置します。

・html
    ・index.html

(2) index.htmlを以下のように編集。
今回は、「これはテストです。」と表示するだけのHTMLです。


<!DOCTYPE html>
<html lang="ja">
  <head>
    <title>タイトル</title>
    <meta charset="utf-8"/>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <meta name="format-detection" content="telephone=no">
  </head>
  <body>
    これはテストです。
  </body>
</html>

(3) htmlフォルダをXcodeのプロジェクトにドラッグ&ドロップし、「Create folder reference」を選択し、「Add to target」でターゲットが選択されてることを確認し、「Finish」ボタンを押す。

画像3

(4) ViewControllerのコードを、以下のように編集。

import UIKit
import WebKit

// ViewController
class ViewController: UIViewController {
    // UI
    var webView: WKWebView!
  
    // ビューのロード時に呼ばれる
    override func viewDidLoad() {
        super.viewDidLoad()
     
        // コンフィギュレーションの準備
        let config = WKWebViewConfiguration()
       
        // Webビューの生成
        self.webView = WKWebView(frame:.zero, configuration: config)
        self.view = self.webView
      
        // ローカルHTMLの表示
        self.loadLocalHtml("index.html")
    }
 
    // ローカルHTMLの表示
    func loadLocalHtml(_ name: String) {
        let url = Bundle.main.url(forResource: name, withExtension: "", subdirectory: "html")!
        self.webView.loadFileURL(url, allowingReadAccessTo: url)
    }
}

5. WKWebViewConfiguration

WKWebViewConfiguration」でWebビューの各種設定を行うことができます。

主なプロパティは、次のとおりです。

◎ Webビューの設定

var websiteDataStore: WKWebsiteDataStore
キャッシュの設定。
・WKWebsiteDataStore.default() : キャッシュあり。
・WKWebsiteDataStore.nonPersistent() : キャッシュなし。

var userContentController: WKUserContentController
SwiftとJavaScriptの双方向通信の設定。

var processPool: WKProcessPool
セッションの設定。

var applicationNameForUserAgent: String?
ユーザーエージェントの設定。

var limitsNavigationsToAppBoundDomains: Bool
ナビゲーションをドメイン内のページに制限するかどうか。

var preferences: WKPreferences
Webビューのコンテンツの設定。
・minimumFontSize : 最小フォントサイズ (ポイント単位)
・tabFocusesLinks : Tabでフォーカスがリンクとフォームコントロールに変わるかどうか
・javaScriptCanOpenWindowsAutomatically : ユーザー操作なしにJavaScriptでウィンドウを開くことができるかどうか
・isFraudulentWebsiteWarningEnabled : 不正疑いのコンテンツに対する警告を表示するかどうか

◎ URLスキームの設定

func setURLSchemeHandler(WKURLSchemeHandler?, forURLScheme: String)
URLスキームに関連付けられたリソースを読み込むオブジェクトを登録。

func urlSchemeHandler(forURLScheme: String) -> WKURLSchemeHandler?
URLスキームに対して現在登録されているハンドラオブジェクトを返す。

◎ レンダリングの設定

var ignoresViewportScaleLimits: Bool
Webページのスケーリングを許可するかどうか。

var suppressesIncrementalRendering: Bool
コンテンツがメモリに完全にロードされるまで、Webビューがコンテンツのレンダリングを抑制するかどうか。

var allowsInlineMediaPlayback: Bool
HTML5ビデオをインラインで再生するかどうか。

var allowsAirPlayForMediaPlayback: Bool
AirPlayを介したメディア再生を許可するかどうか。

var allowsPictureInPictureMediaPlayback: Bool
HTML5ビデオがPictureinPictureを再生できるかどうか。

var mediaTypesRequiringUserActionForPlayback: WKAudiovisualMediaTypes
再生開始するためにユーザー操作を必要とするメディアタイプの取得。
・.audio : オーディオ
・.video : ビデオ
・.all : 全て

◎ データ検出器の設定

var dataDetectorTypes: WKDataDetectorTypes
コンテンツに適用するデータ検出器種別の取得。
・phoneNumber: テキスト内の電話番号を検出し、リンクに変換。
・link: テキスト内のURLを検出し、リンクに変換。
・address: テキスト内の住所を検出し、リンクに変換。
・calendarEvent: テキスト内の日付と時刻を検出し、リンクに変換。
・trackingNumber: テキスト内の追跡番号を検出し、リンクに変換。
・flightNumber: テキスト内のフライト番号を検出し、リンクに変換。
・lookupSuggestion: Spotlightの提案を検出し、リンクに変換。
・all: すべてのデータ型を検出し、リンクに変換。

◎ コンテンツの選択粒度の設定

var selectionGranularity: WKSelectionGranularity
コンテンツの選択粒度の取得。

◎ ユーザーインターフェイスの方向の設定

var userInterfaceDirectionPolicy: WKUserInterfaceDirectionPolicy
ユーザーインターフェイスの方向の取得。
・content: CSS/HTML/XHTML仕様に従う。
・system: ビューのユーザーインターフェイスレイアウトに従う。


「キャッシュなし」と「ユーザーエージェント」を設定するコードは、次のとおりです。

import UIKit
import WebKit

// ViewController
class ViewController: UIViewController {
    // UI
    var webView: WKWebView!
  
    // ビューのロード時に呼ばれる
    override func viewDidLoad() {
        super.viewDidLoad()
     
        // コンフィギュレーションの準備
        let config = WKWebViewConfiguration()
        config.websiteDataStore = WKWebsiteDataStore.nonPersistent() // キャッシュなし
        config.applicationNameForUserAgent = "CustomUserAgent" // ユーザーエージェント

        // Webビューの生成
        self.webView = WKWebView(frame:.zero, configuration: config)
        self.view = self.webView
      
        // HTMLの表示
        self.load("https://www.google.com/")
    }
 
    // HTMLの表示
    private func load(_ url: String) {
        self.webView.load(URLRequest(url: URL(string: url)!))
    }
}

6. WKUIDelegate

WKUIDelegate」は、WebビューのUIを制御するデリゲートです。主にウィンドウ、ダイアログ(alert, confirm, prompt)、コンテキストメニューの3つを制御します。ウィンドウ生成時には、コンフィギュレーションの設定を行うこともできます。

主なメソッドは、次のとおりです。

◎ ウィンドウの制御

func webView(WKWebView, createWebViewWith: WKWebViewConfiguration, for: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView?
ウィンドウのオープン時に呼ばれる。

func webViewDidClose(WKWebView)
ウィンドウのクローズ時に呼ばれる。

◎ ダイアログの制御

func webView(WKWebView, runJavaScriptAlertPanelWithMessage: String, initiatedByFrame: WKFrameInfo, completionHandler: () -> Void)
alertダイアログの表示時に呼ばれる。

func webView(WKWebView, runJavaScriptConfirmPanelWithMessage: String, initiatedByFrame: WKFrameInfo, completionHandler: (Bool) -> Void)
confirmダイアログの表示時に呼ばれる。

func webView(WKWebView, runJavaScriptTextInputPanelWithPrompt: String, defaultText: String?, initiatedByFrame: WKFrameInfo, completionHandler: (String?) -> Void)
promptダイアログの表示時に呼ばれる。

func webView(WKWebView, runOpenPanelWith: WKOpenPanelParameters, initiatedByFrame: WKFrameInfo, completionHandler: ([URL]?) -> Void)
ファイルアップロードダイアログの表示時に呼ばれる。

◎ コンテキストメニューの表示

func webView(WKWebView, contextMenuConfigurationForElement: WKContextMenuElementInfo, completionHandler: (UIContextMenuConfiguration?) -> Void)
コンテキストメニューの開始時に呼ばれる

func webView(WKWebView, contextMenuForElement: WKContextMenuElementInfo, willCommitWithAnimator: UIContextMenuInteractionCommitAnimating)
コンテキストメニューのアニメーターの提供時に呼ばれる。

func webView(WKWebView, contextMenuWillPresentForElement: WKContextMenuElementInfo)
コンテキストメニューのオープン時に呼ばれる。

func webView(WKWebView, contextMenuDidEndForElement: WKContextMenuElementInfo)
コンテキストメニューのクローズ時に呼ばれる。


alertダイアログを表示するコードは、次のとおりです。

import UIKit
import WebKit

// ViewController
class ViewController: UIViewController, WKUIDelegate {
    // UI
    var webView: WKWebView!
   
    // ビューのロード時に呼ばれる
    override func viewDidLoad() {
        super.viewDidLoad()
     
        // コンフィギュレーションの準備
        let config = WKWebViewConfiguration()
        
        // Webビューの生成
        self.webView = WKWebView(frame:.zero, configuration: config)
        self.view = self.webView
        
        // UIデリゲートの指定
        self.webView.uiDelegate = self
      
        // HTMLの表示
        self.load("https://www.google.com/")
    }
   
    // Webページの表示
    private func load(_ url: String) {
        self.webView.load(URLRequest(url: URL(string: url)!))
    }
   
    // alertダイアログの表示時に呼ばれる
    func webView(_ webView: WKWebView,
        runJavaScriptAlertPanelWithMessage message: String,
        initiatedByFrame frame: WKFrameInfo,
        completionHandler: @escaping () -> Void) {
        let alertController = UIAlertController(
            title: "", message: message, preferredStyle: .alert)
        alertController.addAction(UIAlertAction(title: "OK", style: .default) {action in
            completionHandler()
        })
        present(alertController, animated: true, completion: nil)
    }
}

7. WKNavigationDelegate

WKNavigationDelegate」は、Webビューのコンテンツの読み込み状況を把握するためのデリゲートです。

主なメソッドは、次のとおりです。

◎ リクエストとレスポンスの許可

func webView(WKWebView, decidePolicyFor: WKNavigationAction, decisionHandler: (WKNavigationActionPolicy) -> Void)
リクエストを許可するか判定。
・.allow : 許可
・.cancel : 拒否

func webView(WKWebView, decidePolicyFor: WKNavigationResponse, decisionHandler: (WKNavigationResponsePolicy) -> Void)
レスポンスを許可するか判定。
・.allow : 許可
・.cancel : 拒否

func webView(WKWebView, didReceive: URLAuthenticationChallenge, completionHandler: (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
ユーザー認証の実行。
・.useCredential : 許可。
・.performDefaultHandling : 未実装。
・.cancelAuthenticationChallenge : キャンセル。
・.rejectProtectionSpace : リジェクト。

◎ コンテンツの読み込み状況

func webView(WKWebView, didStartProvisionalNavigation: WKNavigation!)
コンテンツの読み込みの準備。

func webView(WKWebView, didCommit: WKNavigation!)
コンテンツの読み込みの開始。

func webView(WKWebView, didFinish: WKNavigation!)
コンテンツの読み込みの完了。

func webView(WKWebView, didFailProvisionalNavigation: WKNavigation!, withError: Error)
HTML読み込み開始時でのエラー発生時(通信圏外など)に呼ばれる。

func webView(WKWebView, didFail: WKNavigation!, withError: Error)
HTML読み込み途中でのエラー発生時(HTML読み込み中のキャンセルなど)に呼ばれる。

func webView(WKWebView, didReceiveServerRedirectForProvisionalNavigation: WKNavigation!)
リダイレクト時に呼ばれる。


コンテンツの読み込み状況をログ出力するコードは、次のとおりです。

import UIKit
import WebKit

// ViewController
class ViewController: UIViewController, WKNavigationDelegate {
    // UI
    var webView: WKWebView!
   
    // ビューのロード時に呼ばれる
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // コンフィギュレーションの準備
        let config = WKWebViewConfiguration()
        
        // Webビューの生成
        self.webView = WKWebView(frame:.zero, configuration: config)
        self.view = self.webView
       
        // ナビゲーションデリゲートの指定
        self.webView.navigationDelegate = self
       
        // Webページの表示
        self.load("https://www.google.com/")
    }
   
    // Webページの表示
    private func load(_ url: String) {
        self.webView.load(URLRequest(url: URL(string: url)!))
    }
   
    // リクエストを許可するか判定
    func webView(_ webView: WKWebView,
        decidePolicyFor navigationAction: WKNavigationAction,
        decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
        print("リクエストを許可するか判定")

        // リクエストURLの取得
        let url = navigationAction.request.url
        print(url!)

        // リクエストの許可(許可:.allow, 拒否:.cancel)
        decisionHandler(.allow)
    }
   
    // レスポンスを許可するか判定
    func webView(_ webView: WKWebView,
        decidePolicyFor navigationResponse: WKNavigationResponse,
        decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
        print("レスポンスを許可するか判定")

        //レスポンスの許可(許可:.allow, 拒否:.cancel)
        decisionHandler(.allow)
    }
   
    // ユーザー認証の実行
    func webView(_ webView: WKWebView,
        didReceive challenge: URLAuthenticationChallenge,
        completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
        print("ユーザー認証の実行")
       
        // ユーザー認証の許可(許可:.useCredential, 未実装:.performDefaultHandling,
        // キャンセル:.cancelAuthenticationChallenge, 拒否:.rejectProtectionSpace)
        completionHandler(.useCredential, nil)
    }

    // コンテンツの読み込みの準備
    func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
        print("コンテンツの読み込みの準備")
    }

    // コンテンツの読み込みの開始
    func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
        print("コンテンツの読み込みの開始")
    }

    // コンテンツの読み込みの完了
    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        print("コンテンツの読み込みの完了")
    }

    // エラー時に呼ばれる
    func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError: Error) {
        print("エラー時に呼ばれる")
    }

    // エラー時に呼ばれる
    func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError: Error) {
        print("エラー時に呼ばれる")
    }

    // リダイレクト時に呼ばれる
    func webView(_ webView: WKWebView, didReceiveServerRedirectForProvisionalNavigation:WKNavigation!) {
        print("リダイレクト時に呼ばれる")
    }
}
リクエストを許可するか判定
https://www.google.com/
コンテンツの読み込みの準備
ユーザー認証の実行
レスポンスを許可するか判定
コンテンツの読み込みの開始
コンテンツの読み込みの完了
ユーザー認証の実行
ユーザー認証の実行

8. SwiftからのJavaScriptの操作

SwiftからJavaScriptを操作するには、Swift側でevaluateJavaScript()を使います。

(1) index.htmlを以下のように編集。


<!DOCTYPE html>
<html lang="ja">
  <head>
    <title>タイトル</title>
    <meta charset="utf-8"/>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <meta name="format-detection" content="telephone=no">
  </head>
  <body>
    これはテストです。<br>
    <p id="output"></p>
  </body>
</html>

(2) ViewControllerのコードを、以下のように編集。

import UIKit
import WebKit

// ViewController
class ViewController: UIViewController, WKNavigationDelegate {
    // UI
    var webView: WKWebView!
   
    // ビューのロード時に呼ばれる
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // コンフィギュレーションの準備
        let config = WKWebViewConfiguration()
        
        // Webビューの生成
        self.webView = WKWebView(frame:.zero, configuration: config)
        self.view = self.webView     
       
        // ナビゲーションデリゲートの指定
        self.webView.navigationDelegate = self
       
        // ローカルHTMLの表示
        self.loadLocalHtml("index.html")
    }
   
    // ローカルHTMLの表示
    func loadLocalHtml(_ name: String) {
       let url = Bundle.main.url(forResource: "index", withExtension: "html", subdirectory: "html")!
       webView.loadFileURL(url, allowingReadAccessTo: url)
    }
   
    // コンテンツの読み込みの完了時に呼ばれる
    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        // SwiftからJavaScriptを操作
        let script = "document.getElementById('output').innerHTML = 'SwiftからのJavaScriptの操作。'"
        self.webView.evaluateJavaScript(script, completionHandler: nil)
    }
}

9. JavaScriptからのSwiftの操作

JavaScriptからSwiftを操作するには、JavaScript側でwindow.webkit.messageHandlers.XXXX.postMessage()を使います。
Swift側はコンテンツコントローラで「WKScriptMessageHandler」を実装します。

(1) index.htmlを以下のように編集。


<!DOCTYPE html>
<html lang="ja">
  <head>
    <title>タイトル</title>
    <meta charset="utf-8"/>
    <meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">   
    <meta name="format-detection" content="telephone=no">
  </head>
 <body>
  これはテストです。
  <script>
    window.webkit.messageHandlers.test.postMessage("JavaScriptからSwiftの操作")
  </script>
 </body>
</html>

(2) ViewControllerのコードを、以下のように編集。

import UIKit
import WebKit

// ViewController
class ViewController: UIViewController, WKScriptMessageHandler {
    // UI
    var webView: WKWebView!
   
    // ビューのロード時に呼ばれる
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // コンフィギュレーションの準備
        let config = WKWebViewConfiguration()
        let userContentController: WKUserContentController = WKUserContentController()
        userContentController.add(self, name: "test") // コンテンツコントローラの追加
        config.userContentController = userContentController
        
        // Webビューの生成
        self.webView = WKWebView(frame:.zero, configuration: config)
        self.view = self.webView
       
        // ローカルHTMLの表示
        self.loadLocalHtml("index.html")
    }
   
    // ローカルHTMLの表示
    func loadLocalHtml(_ name: String) {
       let url = Bundle.main.url(forResource: "index", withExtension: "html", subdirectory: "html")!
       webView.loadFileURL(url, allowingReadAccessTo: url)
    }
    
    // JavaScriptからSwiftを操作
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        if (message.name == "test") {
             print(message.body)
        }
    }
}

10. AutoLayoutの設定

これまで、WKWebViewをViewControllerのルートとして使用してきました。今回は、任意のビューに追加し、AutoLayoutの設定を行い、画面サイズに応じてリサイズするように設定します。

AutoLayoutの設定の手順は、次のとおりです。

(1) translatesAutoresizingMaskIntoConstraintsにfalseを指定。
AutoLayout以前に使われていた、Autosizingのレイアウトの仕組みをAutoLayoutに変換するかどうかのフラグです。

self.webView.translatesAutoresizingMaskIntoConstraints = false

(2) 親ビューと子ビューのフレームを一致させる。

self.webView.frame = self.view.frame

(3) 4方向(top, bottom , leading, tailing)が等しくなる制約を追加。

self.webView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
self.webView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
self.webView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
self.webView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true


コードは、次のとおりです。

import UIKit
import WebKit

// ViewController
class ViewController: UIViewController {
    // UI
    var webView: WKWebView!
   
    // ビューのロード時に呼ばれる
    override func viewDidLoad() {
        super.viewDidLoad()
      
        // コンフィギュレーションの準備
        let config = WKWebViewConfiguration()
        
        // Webビューの生成
        self.webView = WKWebView(frame:.zero, configuration: config)
        self.view.addSubview(self.webView)

        // AutoLayoutの設定
        self.webView.translatesAutoresizingMaskIntoConstraints = false
        self.webView.frame = self.view.frame
        self.webView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
        self.webView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
        self.webView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
        self.webView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
       
        // HTMLの表示
        self.load("https://www.google.com/")
    }
  
    // HTMLの表示
    private func load(_ url: String) {
        self.webView.load(URLRequest(url: URL(string: url)!))
    }
}

11. Webビューのキャッシュ削除

◎ Webビューの全キャッシュ削除

// Webビューの全キャッシュ削除
func removeWebViewCache(_ completion: (() -> Void)!) {
    WKWebsiteDataStore.default().removeData(
        ofTypes: WKWebsiteDataStore.allWebsiteDataTypes(),
        modifiedSince: Date(timeIntervalSince1970: 0),
        completionHandler: completion)
}

◎ Webビューの個別キャッシュ削除

// Webビューの個別キャッシュ削除
func removeWebViewCache(_ completion: (() -> Void)!) {
    let types: Set<String>  = [
        WKWebsiteDataTypeCookies,
        WKWebsiteDataTypeMemoryCache,
        WKWebsiteDataTypeDiskCache,
        WKWebsiteDataTypeOfflineWebApplicationCache,
        WKWebsiteDataTypeLocalStorage,        
        WKWebsiteDataTypeSessionStorage,
        WKWebsiteDataTypeWebSQLDatabases,
        WKWebsiteDataTypeIndexedDBDatabases]
    WKWebsiteDataStore.default().removeData(
        ofTypes: types,
        modifiedSince: Date(timeIntervalSince1970: 0),
        completionHandler: completion)
}
◎ クッキー
・WKWebsiteDataTypeCookies : クッキー

◎ キャッシュ
・WKWebsiteDataTypeMemoryCache
: インメモリキャッシュ
・WKWebsiteDataTypeDiskCache : オンディスクキャッシュ
・WKWebsiteDataTypeOfflineWebApplicationCache : HTMLオフラインWebアプリのキャッシュ

◎ ストレージ
・WKWebsiteDataTypeLocalStorage
: HTMLローカルストレージ
・WKWebsiteDataTypeSessionStorage : HTMLセッションストレージ

◎ データベース
・WKWebsiteDataTypeWebSQLDatabases
: WebSQLデータベース
・WKWebsiteDataTypeIndexedDBDatabases : データベース

12. カスタムWebビュー

よく使うする機能を追加したカスタムWebビューの例は、次のとおりです。

画像3

・loadHtml(_ url: String) - HTMLの読み込み
・loadLocalHtml(_ url: String) - ローカルHTMLの読み込み

・alert()

・confirm()
・prompt()

・console.log() - JavaScriptコンソール
・window.webkit.messageHandlers.event.postMessage(str) - 汎用イベント
・通信エラーダイアログ

・WebView.swift

import WebKit

// カスタムWebビュー
class WebView : WKWebView, WKNavigationDelegate, WKUIDelegate, WKScriptMessageHandler {
    public var eventDelegate: ((String) -> Void)! // イベントデリゲート
   
    // 初期化
    init(frame: CGRect, configuration: WKWebViewConfiguration, useRedirectCookieHandling: Bool = false) {
        super.init(frame: frame, configuration: configuration)
       
        // JavaScriptコンソール
        configuration.userContentController.add(self, name: "logging")
        let _override = WKUserScript(
            source: "var console = { log: function(msg){window.webkit.messageHandlers.logging.postMessage(msg) }};",
            injectionTime: .atDocumentStart, forMainFrameOnly: true)
        configuration.userContentController.addUserScript(_override)
       
        // 汎用イベント
        configuration.userContentController.add(self, name: "event")
       
        // デリゲート
        self.navigationDelegate = self
        self.uiDelegate = self
    }

    // 初期化
    required init?(coder: NSCoder) {
        super.init(coder: coder)
    }
   
    // JavaScriptからSwiftを操作
    public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        // JavaScriptコンソール
        if message.name == "logging" {
            print(message.body)
        }
        // 汎用イベント
        else if message.name == "event" {
            if self.eventDelegate != nil {
                self.eventDelegate(message.body as! String)
            }
        }
    }
   
    // HTMLの読み込み
    func loadHtml(_ url: String) {
        self.load(URLRequest(url: URL(string: url)!))
    }
 
    // ローカルHTMLの読み込み
    func loadLocalHtml(_ name: String) {
       let url = Bundle.main.url(forResource: name, withExtension: "", subdirectory: "html")!
        self.loadFileURL(url, allowingReadAccessTo: url)
    }
   
    // 親のビューコントローラの取得
    func parentViewController() -> UIViewController? {
        var parentResponder: UIResponder? = self
        while true {
            guard let nextResponder = parentResponder?.next else {return nil}
            if let viewController = nextResponder as? UIViewController {
                return viewController
            }
            parentResponder = nextResponder
        }
    }
   
    // alertダイアログの表示時に呼ばれる
    func webView(_ webView: WKWebView,
        runJavaScriptAlertPanelWithMessage message: String,
        initiatedByFrame frame: WKFrameInfo,
        completionHandler: @escaping () -> Void) {
        let alertController = UIAlertController(
            title: "", message: message, preferredStyle: .alert)
        alertController.addAction(UIAlertAction(title: "OK", style: .default) {action in
            completionHandler()
        })
        self.parentViewController()?.present(alertController, animated: true, completion: nil)
    }

    // confirmダイアログの表示時に呼ばれる
    func webView(_ webView: WKWebView,
        runJavaScriptConfirmPanelWithMessage message: String,
        initiatedByFrame frame: WKFrameInfo,
        completionHandler: @escaping (Bool) -> Void) {
        let alertController = UIAlertController(
            title: "", message: message, preferredStyle: .alert)
        alertController.addAction(UIAlertAction(title: "キャンセル", style: .cancel) {action in
            completionHandler(false)
        })
        alertController.addAction(UIAlertAction(title: "OK", style: .default) {action in
            completionHandler(true)
        })
        self.parentViewController()?.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: "", message: prompt, preferredStyle: .alert)
        alertController.addTextField() {$0.text = defaultText}
        alertController.addAction(UIAlertAction(title: "キャンセル", style: .cancel) {action in
            completionHandler("")
        })
        alertController.addAction(UIAlertAction(title: "OK", style: .default) {action in
            if let textField = alertController.textFields?.first {
                completionHandler(textField.text)
            } else {
                completionHandler("")
            }
        })
        self.parentViewController()?.present(alertController, animated: true, completion: nil)
    }
    
    // エラー時に呼ばれる
    func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError: Error) {
        print(withError.localizedDescription)
        showError()
    }

    // エラー時に呼ばれる
    func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError: Error) {
        print(withError.localizedDescription)
        showError()
    }

    // エラーの表示
    func showError() {
        let alertController = UIAlertController(
            title: "", message: "通信失敗しました。", preferredStyle: .alert)
        alertController.addAction(UIAlertAction(title: "OK", style: .default) {action in
        })
        self.parentViewController()?.present(alertController, animated: true, completion: nil)
    }
}

・テスト用HTML

<!DOCTYPE html>
<html lang="ja">
  <head>
    <title>タイトル</title>
    <meta charset="utf-8"/>
    <meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
    <meta name="format-detection" content="telephone=no">
  </head>
  <body>
    カスタムWebビューのテストです。<br>
    <input type="button" value="alert" onclick="alert('alertのテストです。')"><br>
    <input type="button" value="confirm" onclick="r=confirm('confirmのテストです。');alert(r)"><br>
    <input type="button" value="prompt" onclick="r=prompt('promptのテストです。');alert(r)"><br>
    <input type="button" value="console.log" onclick="console.log('console.log()のテストです。')"><br>
    <input type="button" value="event" onclick="window.webkit.messageHandlers.event.postMessage('eventのテストです。')"><br>
  </body>
</html>

次回


この記事が気に入ったらサポートをしてみませんか?