くま's Tech系Blog

基本的には技術で学んだことを書き留めようと思います。雑談もやるかもね!

iOSのWebViewについて

今回はiOSでのWebViewについてまとめます

AndroidのWebViewについては下記まとめていますので、気になる方はみてください!!

kumaskun.hatenablog.com

※今回はUIkitです。SwiftUIはこの記事に追加するか、別で記事にします🙇

Webページを表示させる

Webページを表示させるにはWKWebViewを使用します。

今回、レイアウトはUIViewにWKWebViewをaddSubViewします。

下記がWKWebViewの基本的な使い方です。

WKWebView を利用する際はUIKitとWebKitをimportします。 WKWebView のinitializeはCGRectとWKWebViewConfigurationを引数にとります。 該当するURLページを開くには、WKWebView の loadメソッドを実行します。

import UIKit
import WebKit

class WebViewController: UIViewController {

    var webView: WKWebView!

    override func viewDidLoad() {
        super.viewDidLoad()

        setupWebView()

        let url = URL(string:"https://www.apple.com")
        // URLRequestオブジェクトを生成
        let myRequest = URLRequest(url: url!)

        // URLをWebView にロード
        webView.load(myRequest)
    }

    private func setupWebView() {
        let userContentController = WKUserContentController()
        userContentController.add(self, name: "testCallBack")
        let webConfiguration = WKWebViewConfiguration()
        
        let customFrame = CGRect(
            origin: CGPoint.zero,
            size: webViewContainer.frame.size
        )
        
        webConfiguration.userContentController = userContentController
        
        webView = WKWebView(frame: customFrame, configuration: webConfiguration)
        webView.translatesAutoresizingMaskIntoConstraints = false
        webView.uiDelegate = self
        webView.navigationDelegate = self
        webView.scrollView.backgroundColor = .clear
        
        // webViewContainerは自前で用意したUIView(名前は自由に設定してください)
        webViewContainer.addSubview(webView)
        webView.topAnchor.constraint(equalTo: webViewContainer.topAnchor).isActive = true
        webView.rightAnchor.constraint(equalTo: webViewContainer.rightAnchor).isActive = true
        webView.leftAnchor.constraint(equalTo: webViewContainer.leftAnchor).isActive = true
        webView.bottomAnchor.constraint(equalTo: webViewContainer.bottomAnchor).isActive = true
        webView.heightAnchor.constraint(equalTo: webViewContainer.heightAnchor).isActive = true
    }
}


extension ViewController: WKUIDelegate {
}


extension ViewController: WKNavigationDelegate {
}

この設定で表示させられます。ここからは細かい設定について説明します。

WebViewの生成の補足

WKWebViewConfiguration

WKWebViewConfigurationはWKWebViewの初期化時に参照される設定プロパティが含まれるクラスです。

WKWebViewConfigurationを使用すると、Webページがレンダリングされるまでの時間、メディア再生の処理方法、ユーザーが選択できるアイテムの細分などのオプションを決定できます。

WKWebViewConfigurationは、WKWebViewが最初に初期化されたときにのみ使用されます。 このクラスを使用して、WKWebViewの作成後にその設定を変更することはできません。

WKUserContentController

WKUserContentControllerJavaScriptからのコールバック受信や、スクリプトをWebビューに挿入する方法を提供します。 必要であれば、WKWebViewConfiguration に生成したWKUserContentControllerを登録する処理を追加してください。

基本的な使い方の箇所で、userContentController.add(self, name: "testCallBack")のようにコールバックを登録しています。 具体的なコールバックの処理は下記のように追加します。

extension WebViewController: WKScriptMessageHandler {
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        if(message.name == "testCallBack") {
             print(" \(message.body)") 
        }
    }
} 

これでJavascriptからメッセージが送られてきたらアプリで受け取って処理を行うことができます。

WKUIDelegateとWKNavigationDelegate

基本的な使い方の箇所でWKUIDelegateWKNavigationDelegateの設定を行いました。

ここでは、WKUIDelegateとWKNavigationDelegateについて補足します。

まず、WKUIDelegateはWebビューのUIを制御するデリゲートです。 主にウィンドウ、Javascriptのダイアログ(alert, confirm, prompt)、コンテキストメニューを制御します。

下記に例を記載します。

extension WebViewController: WKUIDelegate {

    // Webビューを作成
    func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
        guard let url = navigationAction.request.url else {
            return nil
        }
        
        guard let targetFrame = navigationAction.targetFrame,
              targetFrame.isMainFrame else {
            // 同一ページでWebを読み込む
            webView.load(URLRequest(url: url))
            return nil
        }
        
        return nil
    }

    // Javascriptの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)
    }
}

他にもデリゲート処理はありますが、公式ドキュメント等を参考にしてみてください。

WKNavigationDelegateはwebビューのナビゲーションのデリゲートです。 遷移開始時・開始時・ページの読み込み完了時・読み込みエラー発生時リダイレクト時などのイベントを受け取りたい場合はWKNavigationDelegateを継承します。

下記に例を記載します。

extension WebViewController: WKNavigationDelegate {
    // リクエストを許可するか判定
    func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
        decisionHandler(.allow)
    }
    
    // コンテンツを読み込んだ時に呼ばれるメソッド
    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
    }
}

この例では、特定のURLのみ許可するのに使用したり、コンテンツを読み込んだ後に何か処理を行う場合に使用します。

HTMLを表示させる

次にHTMLを表示させる方法です

HTMLファイルを置いたら、Webページと同じように、ロードするだけで表示できます。

func loadLocalHTML() {
    guard let path: String = Bundle.main.path(forResource: "index", ofType: "html") else { return }
    let localHTMLUrl = URL(fileURLWithPath: path, isDirectory: false)
    webView.loadFileURL(localHTMLUrl, allowingReadAccessTo: localHTMLUrl)
}

Bundle.main.pathはプロジェクトのBuild PhasesのCopy Bundle Resourcesにあるファイルを取得するはずです。

また、HTMLのファイルではなく、文字列を読み込む場合にはloadHTMLStringメソッドを使用します。

func loadLocalHTML() {
    let webView = WKWebView()
    webView.loadHTMLString("<html><body><p>Hello!</p></body></html>", baseURL: nil)
}

CSSの適応

CSSを適用させるにはページ読み込み完了したらCSSファイルを読み込む処理を行います

まずはHTML読み込み時にCSSも読み込むパターンです。

private func loadHTML() {
        guard let html = Bundle.main.url(forResource: "index"(ファイル名), withExtension: "html")  else { return }
        guard let htmlString = try? String(contentsOf: html) else { return }

        /// CSSのファイルURLを取得する
        guard let css = Bundle.main.url(forResource: "style"(ファイル名), withExtension: "css")  else { return }

        webView.loadHTMLString(htmlString, baseURL: css)
 }

ただ、CSSファイルを読み込むパターンはうまく適用されない場合もあります。

そこで、HTML表示後にCSSを適用させるパターンも紹介します。

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        let css = "img {max-width: 100%; width: 100%; height: 75%; vertical-align: middle;}"
        
        let js = "var style = document.createElement('style'); style.innerHTML = '\(css)'; document.head.appendChild(style);"
        
        cssWebView.evaluateJavaScript(js, completionHandler: nil)
}

WKNavigationDelegateのdidFinish(表示完了時)にCSSを含めたJavascriptを実行する方法です。 evaluateJavaScriptメソッドはJavascriptを実行するメソッドです。

また、下記のようにスクリプトを追加することで表示前にCSSを適用するパターンもあります。

let script = WKUserScript(source: "JavaScriptコード", injectionTime: .atDocumentEnd, forMainFrameOnly: true)

webView.configuration.userContentController.addUserScript(script)

let controller = WKUserContentController()
controller.addUserScript(script)
let configuration = WKWebViewConfiguration()
configuration.userContentController = controller
let webView = WKWebView(frame: view.bounds, configuration: configuration)

CSSの適用は場合によってはできない場合にもあるので注意が必要です。

Javascriptとの連携

Javascriptからアプリの処理を実行したり、アプリからJavaScriptのメソッドを実行したい場合があると思います。 それぞれの方法を紹介します。

Javascriptからアプリの処理を実行する

Javascriptでボタンのタップアクションをアプリ側で受け取りアプリで処理を行う方法について説明します。 WKUserContentControllerの説明と多少、重複します。

HTMLは次の例を使用します。

<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="UTF-8">
    </head>
    <body>
        <script type="text/javascript">
            function showMessage() {
                webkit.messageHandlers.showMessage.postMessage("");
            }
            function setMessage(message) {
                document.getElementById("text").innerHTML = message;
            }
        </script>
        <input type="button" value="Button"
               onClick="showMessage();" />
        <p id="text"></p>
    </body>
</html>

基本的な使い方の箇所でコールバックの設定を行いましたが、同様にハンドラを追加します。

let config: WKWebViewConfiguration = WKWebViewConfiguration()
let controller: WKUserContentController = WKUserContentController()

// JavaScriptから呼び出せるメッセージハンドラを設定する
controller.add(self, name: "showMessage")

config.userContentController = controller
webView = WKWebView(frame: self.view.bounds, configuration: config)

// 既にwebViewを初期化している場合は、こちらでもよい
webView.configuration.userContentController.add(self, name: "showMessage")

WKScriptMessageHandlerに準拠して、上で定義したメッセージハンドラをJavaScriptから呼び出すと、userContentController(_:didReceive:)が実行され、メッセージに応じた処理ができます。

extension WebViewController: WKScriptMessageHandler {
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        if message.name == "showMessage" {
            print("\\\\(message.body)")
        }
    }
}

message.bodyはHTMLでwebkit.messageHandlers.showMessage.postMessage("");の箇所のpostMessageで設定した文字列になります。

アプリからJavaScriptのメソッドを実行する

今度は先程と逆でアプリからJavascriptを実行する方法です。

次のようにevaluateJavascriptメソッドでJavascriptの関数を呼び出します

let message = "Message"

let jsFunc = "test(\\\\"\\\\(message)\\\\")"

// JavaScriptメソッドを実行する
webView.evaluateJavaScript(jsFunc, completionHandler: { (object, error) -> Void in
    // JavaScriptメソッドの実行結果を受け取れる
})

参照

developer.apple.com

sogablog.net

gekkado.com

qiita.com