In the following code, I'm loading HTML pages into a WKWebView, only to get the contents of their first H1 tag. Other than what any type of page renders in its first H1 tag, I don't need anything else...
Is there perhaps a more efficient/performant way besides initializing a WKWebView for this?
(the .frame(width: 0, height: 0)
modifier annoys me a bit, as well)
import SwiftUI
import WebKit
import WebView /* add this Swift Package Dependency https://github.com/kylehickinson/SwiftUI-WebView */
struct ContentView: View {
@StateObject private var viewModel: ContentViewModel
init() {
let viewModel = ContentViewModel()
_viewModel = StateObject(wrappedValue: viewModel)
}
var body: some View {
VStack(spacing: 30) {
Button("load plain H1 tag") {
viewModel.webViewStore.webView.loadHTMLString("<h1>H1</h1>", baseURL: nil)
}
Button("load body where h1 is appended by JavaScript") {
viewModel.webViewStore.webView.loadHTMLString("<body></body><script>(function(){ let h1 = document.createElement('h1'); h1.textContent = 'H1 JavaScript'; document.body.appendChild(h1); })()</script>", baseURL: nil)
}
Button("load apple.com/iphone") {
viewModel.webViewStore.webView.load(URLRequest(url: URL(string: "https://apple.com/iphone")!))
}
Button("load hackingwithswift.com") {
viewModel.webViewStore.webView.load(URLRequest(url: URL(string: "https://hackingwithswift.com")!))
}
Group {
Text("the first H1 element on ")
+ Text(viewModel.webViewStore.webView.url?.description ?? "...").foregroundColor(.accentColor)
+ Text(" is")
}
.font(.subheadline)
Text(viewModel.h1)
.font(.largeTitle)
.foregroundColor(.green)
WebView(webView: viewModel.webViewStore.webView)
.frame(width: 0, height: 0)
}
}
}
class ContentViewModel: NSObject, WKScriptMessageHandler, ObservableObject {
@Published var h1 = "..."
@Published var webViewStore: WebViewStore
override init() {
let userContentController = WKUserContentController()
let configuration = WKWebViewConfiguration()
let userScript = WKUserScript(
source: """
function elementReady(selector) {
return new Promise((resolve, reject) => {
let el = document.querySelector(selector)
if (el) {
resolve(el)
}
new MutationObserver((mutationRecords, observer) => {
Array.from(document.querySelectorAll(selector)).forEach((element) => {
resolve(element)
observer.disconnect()
})
}).observe(document.documentElement, {
childList: true,
subtree: true,
})
})
}
elementReady("h1").then((titleElement) => {
window.webkit.messageHandlers.h1.postMessage(titleElement.textContent)
})
""",
injectionTime: .atDocumentStart,
forMainFrameOnly: false,
in: .defaultClient
)
userContentController.addUserScript(userScript)
configuration.userContentController = userContentController
let webView = WKWebView(frame: .zero, configuration: configuration)
webViewStore = WebViewStore(webView: webView)
super.init()
userContentController.add(self, contentWorld: .defaultClient, name: "h1")
}
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if message.name == "h1" {
h1 = message.body as! String
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}