LoginSignup
6
5

More than 1 year has passed since last update.

【SwiftUI】WKWebViewをSwiftUIで使う

Last updated at Posted at 2022-11-19

はじめに

SwiftUIも便利になってきましたが、WebViewはいまだに対応されていません。
今回はSwiftUIでWKWebViewを使用する方法を紹介します。

やりたいこと

  • カスタムユーザーエージェントを設定
  • WKWebViewConfigurationを使ってJavaScriptを実行
  • URLを監視

この辺りができれば、UIKitで使用する時と同等のことができるのではないでしょうか

参考

ベースはこちらのライブラリです。
WKWebViewConfigurationの設定ができなかったのでPRを投げてみました。

実装

#if os(iOS)
typealias WebViewRepresentable = UIViewRepresentable
#elseif os(macOS)
typealias WebViewRepresentable = NSViewRepresentable
#endif

#if os(iOS) || os(macOS)
import SwiftUI
import WebKit

public struct WebView: WebViewRepresentable {
    
    // MARK: - Initializers
    public init(
        url: URL? = nil,
        configuration: WKWebViewConfiguration? = nil,
        webView: @escaping (WKWebView) -> Void = { _ in }) {
        self.url = url
        self.configuration = configuration
        self.webView = webView
    }

    // MARK: - Properties
    
    private let url: URL?
    private let configuration: WKWebViewConfiguration?
    private let webView: (WKWebView) -> Void

    // MARK: - Functions
    
    #if os(iOS)
    public func makeUIView(context: Context) -> WKWebView {
        makeView()
    }
    
    public func updateUIView(_ uiView: WKWebView, context: Context) {}
    #endif
    
    #if os(macOS)
    public func makeNSView(context: Context) -> WKWebView {
        makeView()
    }
    
    public func updateNSView(_ view: WKWebView, context: Context) {}
    #endif
}

private extension WebView {

    func makeWebView() -> WKWebView {
        guard let configuration = self.configuration else { return WKWebView() }
        return WKWebView(frame: .zero, configuration: configuration)
    }
    
    func makeView() -> WKWebView {
        let view = makeWebView()
        webView(view)
        tryLoad(url, into: view)
        return view
    }
    
    func tryLoad(_ url: URL?, into view: WKWebView) {
        guard let url = url else { return }
        view.load(URLRequest(url: url))
    }
}
#endif

使い方

基本的な使い方

ContentView
import SwiftUI

struct ContentView: View {
    @State var isPresentedWebView: Bool = false

    private let url = "https://gs.statcounter.com/detect"
    var body: some View {
        VStack {
            Button {
                isPresentedWebView = true
            } label: {
                Text("Show WebView")
            }
        }
        .sheet(isPresented: $isPresentedWebView) {
+           WebView(url: URL(string: url))
        }
    }
}

カスタムユーザーエージェントの設定

ContentView
import SwiftUI

struct ContentView: View {
    @StateObject var viewModel = ViewModel()

    @State var isPresentedWebView: Bool = false

    private let url = "https://taishin-miyamoto.com"
    var body: some View {
        VStack {
            Button {
                isPresentedWebView = true
            } label: {
                Text("Show WebView")
            }
        }
        .sheet(isPresented: $isPresentedWebView) {
+           WebView(url: URL(string: url)) { webView in
+               let customUserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3864.0 Safari/537.36"
+               webView.customUserAgent = customUserAgent
+           }
        }
    }
}

WKWebViewConfigurationを使ってJavaScriptを実行

JavaScriptはこちらをサンプルとして使いました

ContentView
import SwiftUI

struct ContentView: View {
    @StateObject var viewModel = ViewModel()

    @State var isPresentedWebView: Bool = false

    private let url = "https://taishin-miyamoto.com"
    var body: some View {
        VStack {
            Button {
                isPresentedWebView = true
            } label: {
                Text("Show WebView")
            }
        }
        .sheet(isPresented: $isPresentedWebView) {
+           WebView(url: URL(string: url), configuration: viewModel.configuration)
        }
    }
}
ViewModel
import WebKit

final class ViewModel: ObservableObject {
    var configuration: WKWebViewConfiguration {
        let disableSelectionScriptString = "document.documentElement.style.webkitUserSelect='none';"
        let disableCalloutScriptString = "document.documentElement.style.webkitTouchCallout='none';"

        let disableSelectionScript = WKUserScript(source: disableSelectionScriptString, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
        let disableCalloutScript = WKUserScript(source: disableCalloutScriptString, injectionTime: .atDocumentEnd, forMainFrameOnly: true)

        let userContentController = WKUserContentController()
        userContentController.addUserScript(disableSelectionScript)
        userContentController.addUserScript(disableCalloutScript)

        let configuration = WKWebViewConfiguration()
        configuration.userContentController = userContentController
        configuration.ignoresViewportScaleLimits = false

        return configuration
    }
}

URLの監視

Combineを使って監視してみたいと思います。

ContentView
import SwiftUI

struct ContentView: View {
    @StateObject var viewModel = ViewModel()

    @State var isPresentedWebView: Bool = false

    private let url = "https://taishin-miyamoto.com"
    var body: some View {
        VStack {
            Button {
                isPresentedWebView = true
            } label: {
                Text("Show WebView")
            }
        }
        .sheet(isPresented: $isPresentedWebView) {
            WebView(url: URL(string: url)) { webView in
                viewModel.observeURL(webView: webView)
            }
        }
    }
}
ViewModel
import Combine
import WebKit

final class ViewModel: ObservableObject {
    private var cancellables = Set<AnyCancellable>()

    func observeURL(webView: WKWebView) {
        webView.publisher(for: \.url, options: .new)
            .sink { url in
                guard let url else { return }
                print(url)
            }
            .store(in: &cancellables)
    }
}

おわり

フルSwiftUIで実装できるのはいつになるのでしょうか。。。

6
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
5