はじめに
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で実装できるのはいつになるのでしょうか。。。