WebView 表示対策

WebViewを使ったアプリを作成しているが、OSや端末等でJavascriptの設定をする必要がある
例えば別タブ、別ウィンドウで開く場合はWebView側で設定が必要
以下にOSごとの対応方法を記述しておく

Android Kotlin & Jetpack Compose

import android.view.ViewGroup
import android.webkit.WebView
import androidx.compose.runtime.Composable
import androidx.compose.ui.viewinterop.AndroidView

// WebView.settingsを設定する
AndroidView(
    factory = { context ->
        WebView(context).apply {
            // WebViewの高さを指定 これを設定しないとCSSのvhが効かなくなる
            layoutParams = ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT
            )
            settings.apply {
                // JavaScript有効化
                settings.javaScriptEnabled = true
                // loadWithOverviewMode, useWideViewPortをtrueにすると別ウィンドウの画面がWebViewの幅に合わせて表示される
                loadWithOverviewMode = true // trueの場合にはHTMLのwidthがWebViewより大きい時に全体を表示するようによしなに調整される
                useWideViewPort = true // ダブルタップによるズームイン・ズームアウトの有効化
            }
        }
    }
)

iOS Swift & SwiftUI

import SwiftUI
import WebKit

struct CustomWebView: UIViewRepresentable {

    func makeUIView(context: Context) -> WKWebView {
        let webView = WKWebView()
        // jsのアラート、確認ダイアログなどのUI処理、新しいページ(target="_blank"など)を開く必要があるときの制御
        webView.uiDelegate = context.coordinator
        // JavaScript有効化
        webView.configuration.defaultWebpagePreferences.allowsContentJavaScript = true

        return webView
    }

    func updateUIView(_ uiView: WKWebView, context: Context) {}

    func makeCoordinator() -> Coordinator {
        Coordinator(parent: self)
    }
}

extension CustomWebView {
    // AndroidでいうWebViewClient
    class Coordinator: NSObject, WKUIDelegate {

        private let parent: CustomWebView

        init(parent: CustomWebView) {
            // parentを設定しておくとCoodinatorから参照できて引数の肥大化を防げる、かも
            self.parent = parent
        }
        // JavaScriptのwindow.openをハンドリング 新しいウィンドウを作らずに今のWebViewで読み込む
        func webView(_ webView: WKWebView,
            createWebViewWith configuration: WKWebViewConfiguration,
            for navigationAction: WKNavigationAction,
            windowFeatures: WKWindowFeatures) -> WKWebView? {
            // 別タブだったら現在のwebViewで表示する実装例
            if navigationAction.targetFrame?.isMainFrame != true {
                webView.load(navigationAction.request)
            }

            return nil
        }
        // JavaScriptのalertイベントをハンドリング
        func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
            let alert = UIAlertController(title: "Hey, Listen!", message: message, preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "OK", style: .default))
            let controller = getRootController()
            controller?.present(alert, animated: true)
            completionHandler()
        }
        // JavaScriptのconfirmイベントをハンドリング
        func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) {
            let alert = UIAlertController(title: nil, message: message, preferredStyle: .alert)
            alert.addAction(.init(title: "OK", style: .default, handler: { _ in
                completionHandler(true)
            }))
            alert.addAction(.init(title: "キャンセル", style: .cancel, handler: { _ in
                completionHandler(false)
            }))
            let controller = getRootController()
            controller?.present(alert, animated: true)
        }
        // JavaScriptのpromptイベントをハンドリング
        func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) {
            let alert = UIAlertController(title: nil, message: prompt, preferredStyle: .alert)
            alert.addTextField { textField in
                textField.text = defaultText
            }
            alert.addAction(.init(title: "OK", style: .default, handler: { _ in
                completionHandler(alert.textFields?.first?.text)
            }))
            alert.addAction(.init(title: "キャンセル", style: .cancel, handler: { _ in
                completionHandler(nil)
            }))
            let controller = getRootController()
            controller?.present(alert, animated: true)
        }
        // アクティブな画面のControllerを取得
        private func getRootController() -> UIViewController? {
            let window = UIApplication.shared.connectedScenes
                .filter { $0.activationState == .foregroundActive }
                .compactMap { $0 as? UIWindowScene }
                .first?.windows.filter { $0.isKeyWindow }.first as UIWindow?
            guard let rootController = window?.rootViewController else {
                return nil
            }
            return rootController
        }
    }
}

swiftのgetRootControllerの内容はリンクの記事を参考にさせていただきました

「The Ultimate Guide to WKWebView」をSwiftUIで実装する #13 - Showing custom UI - - Qiita
「The Ultimate Guide to WKWebView」をSwiftUIで実装してみるの、 13個目になります。12個目を投稿以降、しばらくサボっていてごめんなさい。 今回はWebViewで新しいウィンドウを開く方法についてです。...

コメント

タイトルとURLをコピーしました