UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Using WKWebView to sync data out of any page with SwiftUI - but is there a better approach?

Forums > SwiftUI

@TD540  

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()
    }
}

3      

Hi,

yea this seems pretty wasteful to have WKWebView load all the data and probably media resources just to get a piece of text.

I think one approach would be to check the JavaScript Core framework whether that can help you? Possibly load the DOM and let you traverse it with JS methods.

The other option is to download the HTML as a text and use some parser to find the first H1 tag, but this won't wotk if the entire website is JavaScript frontend. You will get just a head.

So maybe I would keep WKWebView but prevent it from loading images.

4      

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.