Initialize member in view struct

Viewed 227

I want to have a WKWebView with JavaScript handling in SwiftUI. From Swift Variables Initialization, I am doing the following: (I am using https://github.com/kylehickinson/SwiftUI-WebView to provide a wrapper for WKWebView in SwiftUI that also add valuable layout constraints.)

struct ContentView: View {

    var body: some View {
        WebView(webView: myWebView)
        .onAppear {
            let url = Bundle.main.url(forResource: "index", withExtension: "html")!
            myWebView.loadFileURL(url, allowingReadAccessTo: url)
            let request = URLRequest(url: url)
            myWebView.load(request)
        }
    }

    class JSHandler : NSObject, WKScriptMessageHandler {
        var contentView: ContentView?

        func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
            print("documentReady")
            contentView!.myWebView.evaluateJavaScript("<Long Script to Transfer Data>") { (result, error) in
            }
        }
    }

    let myJSHandler = JSHandler()

    var myWebView: WKWebView = {
        let config = WKWebViewConfiguration()
        let controller = WKUserContentController()
        controller.add(myJSHandler , name: "documentReady")
        config.userContentController = controller
        return WKWebView(frame: .zero, configuration: config)
    }()
}

But then I learned from Instance member cannot be used on type that this doesn't work because the closure doesn't have reference to self. I need the WKWebView to have dedicated config object so I can't just use the other constructor. I need a reference to it to do evaluateJavaScript.

How to make this work?

EDIT 1: Add the body and mention the framework used to wrap WKWebView.

EDIT 2: Added code to clarify that I need two way communications from WKWebView to native app via WKScriptMessageHandler (to get notified when the HTML document is ready) and from native app to WKWebView via evaluateJavaScript (to transfer data upon the HTML document is ready).

2 Answers

One possible solution is to configure WKWebView in init

struct ContentView: View {
    private var myWebView: WKWebView

    init() {
        let config = WKWebViewConfiguration()
        let controller = WKUserContentController()
        controller.add(myJSHandler , name: "documentReady")
        config.userContentController = controller
        myWebView = WKWebView(frame: .zero, configuration: config)
    }

    var body: some View {
        WebView(webView: myWebView)
        .onAppear {
            let url = Bundle.main.url(forResource: "index", withExtension: "html")!
            myWebView.loadFileURL(url, allowingReadAccessTo: url)
            let request = URLRequest(url: url)
            myWebView.load(request)
        }
    }

    class JSHandler : NSObject, WKScriptMessageHandler {

        func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
            print("documentReady")
            message.webView?.evaluateJavaScript("<Long Script to Transfer Data>") { (result, error) in
            }
        }
    }

    let myJSHandler = JSHandler()

}

Just make myWebView a lazy variable, This makes sure that the jsHandler is initialised before WebView.

lazy var myWebView: WKWebView = {
    let config = WKWebViewConfiguration()
    let controller = WKUserContentController()
    controller.add(myJSHandler , name: "documentReady")
    config.userContentController = controller
    return WKWebView(frame: .zero, configuration: config)
}()
Related