Toma
Toma

Reputation: 2936

SwiftUI coordinator not updating the containing view's property

So I've wrapped WKWebView in an UIViewRepresentable and built a coordinator in order to access its navigation delegate's functions. In the webView(_:didFinish:) function I am trying to update the view's didFinishLoading variable. If I print right after assigning, it prints true - the expected behavior. But, in the parent view, when I call the getHTML function, it prints false - even if I wait until the WKWebView is fully loaded. Here is the code:

import SwiftUI
import WebKit

struct WebView: UIViewRepresentable {
    @Binding var link: String

    init(link: Binding<String>) {
        self._link = link
    }

    private var didFinishLoading: Bool = false

    let webView = WKWebView()

    func makeUIView(context: UIViewRepresentableContext<WebView>) -> WKWebView {
        self.webView.load(URLRequest(url: URL(string: self.link)!))
        self.webView.navigationDelegate = context.coordinator
        return self.webView
    }

    func updateUIView(_ uiView: WKWebView, context: UIViewRepresentableContext<WebView>) {
        return
    }

    class Coordinator: NSObject, WKNavigationDelegate {
        private var webView: WebView

        init(_ webView: WebView) {
            self.webView = webView
        }

        func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
            print("WebView: navigation finished")
            self.webView.didFinishLoading = true
        }
    }

    func makeCoordinator() -> WebView.Coordinator {
        Coordinator(self)
    }

    func getHTML(completionHandler: @escaping (Any?) -> ()) {
        print(self.didFinishLoading)
        if (self.didFinishLoading) {
            self.webView.evaluateJavaScript(
                """
                document.documentElement.outerHTML.toString()
                """
            ) { html, error in
                    if error != nil {
                        print("WebView error: \(error!)")
                        completionHandler(nil)
                    } else {
                        completionHandler(html)
                    }
            }
        }
    }
}

struct WebView_Previews: PreviewProvider {
    @State static var link = "https://apple.com"
    static var previews: some View {
        WebView(link: $link)
    }
}

Upvotes: 2

Views: 3730

Answers (1)

Asperi
Asperi

Reputation: 257869

Here is your code, a bit modified for demo, with used view model instance of ObservableObject holding your loading state.

import SwiftUI
import WebKit
import Combine

class WebViewModel: ObservableObject {
    @Published var link: String
    @Published var didFinishLoading: Bool = false
    
    init (link: String) {
        self.link = link
    }
}

struct WebView: UIViewRepresentable {
    @ObservedObject var viewModel: WebViewModel

    let webView = WKWebView()

    func makeUIView(context: UIViewRepresentableContext<WebView>) -> WKWebView {
        self.webView.navigationDelegate = context.coordinator
        if let url = URL(string: viewModel.link) {
            self.webView.load(URLRequest(url: url))
        }
        return self.webView
    }

    func updateUIView(_ uiView: WKWebView, context: UIViewRepresentableContext<WebView>) {
        return
    }

    class Coordinator: NSObject, WKNavigationDelegate {
        private var viewModel: WebViewModel

        init(_ viewModel: WebViewModel) {
            self.viewModel = viewModel
        }

        func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
            print("WebView: navigation finished")
            self.viewModel.didFinishLoading = true
        }
    }

    func makeCoordinator() -> WebView.Coordinator {
        Coordinator(viewModel)
    }

}

struct WebViewContentView: View {
    @ObservedObject var model = WebViewModel(link: "https://apple.com")

    var body: some View {
        VStack {
            TextField("", text: $model.link)
            WebView(viewModel: model)
            if model.didFinishLoading {
                Text("Finished loading")
                    .foregroundColor(Color.red)
            }
        }
    }
}

struct WebView_Previews: PreviewProvider {
    static var previews: some View {
        WebViewContentView()
    }
}

Upvotes: 8

Related Questions