使用 swiftUI 将更新发送到子视图(WKWebView)?

Send update to child view (WKWebView) using swiftUI?

我想使用 SwiftUI 在视图中制作一个非常简单的浏览器。

期待什么:

在我的ContentView中,有一个WKWebView,在Navigation Bar的左边有一个“Go Back”按钮。如果按下“返回”按钮,webView 将返回到上一页。

ContentView.swift:

struct ContentView: View {
    let defaultUrl = "https://www.apple.com"
    @State var needsGoBack = false
    
    var body: some View {
        WebView(urlString: defaultUrl, needsGoBack: $needsGoBack)
            .navigationBarItems(leading:
                Button(action: {
                    print("button pressed...set needsGoBack = true")
                    needsGoBack = true
                }) {
                    Text("Go Back")
                })
    }
}

WebView.swift

struct WebView: UIViewRepresentable {
    
    let urlString: String
    let navigationHelper = WebViewHelper()
    @State var myWebView = WKWebView()
    @Binding var needsGoBack: Bool
    
    func makeUIView(context: Context) -> WKWebView {
        if let url = URL(string: urlString) {
            let request = URLRequest(url: url)
            myWebView.load(request)
        }
        
        myWebView.navigationDelegate = navigationHelper
        myWebView.uiDelegate = navigationHelper
        
        return myWebView
    }
    
    func updateUIView(_ uiView: WKWebView, context: Context) {
        print("webview updateUIView")
        
        print("needsGoBack", needsGoBack)
        
        if needsGoBack {
            myWebView.goBack()
            needsGoBack = false // this line has problem
        }
    }
    
    typealias UIViewType = WKWebView
    
}

// https://gist.github.com/joshbetz/2ff5922203240d4685d5bdb5ada79105
class WebViewHelper: NSObject, WKNavigationDelegate, WKUIDelegate {
    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        print("webview didFinishNavigation")
    }
    
    func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
        print("didStartProvisionalNavigation")
    }
    
    func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
        print("webviewDidCommit")
    }
    
    func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
        print("didReceiveAuthenticationChallenge")
        completionHandler(.performDefaultHandling, nil)
    }
}

实际发生了什么:

在 WebView.swift 的 updateUIView 函数中,有一条警告消息“在视图更新期间修改状态,这将导致未定义的行为。”对于这行代码:“needsGoBack = false”。并且“needsGoBack”变量不会设置为“false”。 “返回”按钮第一次起作用。但是,因为“needsGoBack”之后一直是“true”,子视图(WebView)将不会被第二次通知(调用updateUIView方法)。

通过将您的 WKWebView 存储在您的 ContentView 都可以访问的单独对象(在本例中为 NavigationState)中,您可以访问 goBack()方法直接。这样,您就避免了尝试使用 Bool 来表示一次性事件的棘手问题,这不仅在实践中不起作用(如您所见),而且在语义上也有点有趣想想。


class NavigationState : NSObject, ObservableObject {
    @Published var currentURL : URL?
    @Published var webView : WKWebView
    
    override init() {
        let wv = WKWebView()
        self.webView = wv

        super.init()
        wv.navigationDelegate = self
    }
    
    func loadRequest(_ urlRequest: URLRequest) {
        webView.load(urlRequest)
    }
}

extension NavigationState : WKNavigationDelegate {
    func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
        self.currentURL = webView.url
    }
}

struct WebView : UIViewRepresentable {
    
    @ObservedObject var navigationState : NavigationState
    
    func makeUIView(context: Context) -> WKWebView  {
        return navigationState.webView
    }
    
    func updateUIView(_ uiView: WKWebView, context: Context) {
        
    }
}

struct ContentView: View {
    @StateObject var navigationState = NavigationState()
    
    var body: some View {
        VStack {
            Text(navigationState.currentURL?.absoluteString ?? "(none)")
            WebView(navigationState: navigationState)
                .clipped()
            HStack {
                Button("Back") {
                    navigationState.webView.goBack()
                }
                Button("Forward") {
                    navigationState.webView.goForward()
                }
            }
        }.onAppear {
            navigationState.loadRequest(URLRequest(url: URL(string: "https://www.google.com")!))
        }
    }
}