SwiftUI 中的 WKWebView - 当用户与网站交互时如何切换视图?

Sch*_*ken 5 webview ios swift wkwebview swiftui

我的情况如下:我有一个 SwiftUI 应用程序,想要显示一个 WebView。当用户点击该 WebView 中的某个按钮时,我希望用户被重定向到下一个 (SwiftUI) 视图。我使用 UIViewRepresentable ,因为这似乎是在 SwiftUI 中显示 WebView 的当前方式,因为它是 UIKit 的桥梁。问题是: UIViewRepresentable 没有body。那么我在哪里告诉视图切换?在通常的 SwiftUI 视图中,我会更新一个模型,然后对主体中的模型更改做出反应。

我设置了一个示例,其中在 WebView 中呈现https://www.google.com。当用户发送搜索查询时,协调器被调用,它调用 UIViewRepresentable 的函数:

视图- 这是应该通过显示另一个视图(使用NavigationLinks实现)对模型更改做出反应的视图

import SwiftUI

struct WebviewContainer: View {
    @ObservedObject var model: WebviewModel = WebviewModel()
    var body: some View {
        return NavigationView {
            VStack {
                NavigationLink(destination: LoginView(), isActive: $model.loggedOut) {
                    EmptyView()
                }.isDetailLink(false)
                .navigationBarTitle(Text(""))
                .navigationBarHidden(self.model.navbarHidden)

                NavigationLink(destination: CameraView(model: self.model), isActive: $model.shouldRedirectToCameraView) {
                    EmptyView()
                }
                .navigationBarTitle(Text(""))
                .navigationBarHidden(self.model.navbarHidden)
                Webview(model: self.model)
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

UIViewControllerRepresentable - 这是在 SwiftUI 上下文中使用 WKWebview 所必需的

import SwiftUI
import WebKit

struct Webview : UIViewControllerRepresentable {
    @ObservedObject var model: WebviewModel

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

    func makeUIViewController(context: Context) -> EmbeddedWebviewController {
        let webViewController = EmbeddedWebviewController(coordinator: context.coordinator)
        webViewController.loadUrl(URL(string:"https://www.google.com")!)

        return webViewController
    }

    func updateUIViewController(_ uiViewController: EmbeddedWebviewController, context: UIViewControllerRepresentableContext<Webview>) {

    }

    func startCamera() {
        model.startCamera()
    }
}
Run Code Online (Sandbox Code Playgroud)

UIViewController - WKNavigationDelegate 对点击“Google 搜索”做出反应并调用协调器

import UIKit
import WebKit

class EmbeddedWebviewController: UIViewController, WKNavigationDelegate {

    var webview: WKWebView
    var router: WebviewRouter? = nil

    public var delegate: Coordinator? = nil

    init(coordinator: Coordinator) {
        self.delegate = coordinator
        self.webview = WKWebView()
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder: NSCoder) {
        self.webview = WKWebView()
        super.init(coder: coder)
    }

    public func loadUrl(_ url: URL) {
        webview.load(URLRequest(url: url))
    }

    override func loadView() {
        self.webview.navigationDelegate = self
        view = webview
    }

    func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
        guard let url = (navigationResponse.response as! HTTPURLResponse).url else {
            decisionHandler(.cancel)
            return
        }

        if url.absoluteString.starts(with: "https://www.google.com/search?") {
            decisionHandler(.cancel)
            self.delegate?.startCamera(sender: self.webview)
        }
        else {
            decisionHandler(.allow)
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
    }
}
Run Code Online (Sandbox Code Playgroud)

Coordinator - WKNavigationDelegateUIViewRepresentable之间的桥梁

import Foundation
import WebKit

class Coordinator: NSObject {
    var webview: Webview

    init(_ webview: Webview) {
        self.webview = webview
    }

    @objc func startCamera(sender: WKWebView) {
        webview.startCamera()
    }
}
Run Code Online (Sandbox Code Playgroud)

更新

我现在有一个带有模型的视图(@ObservedObject)。这个模型被赋予UIViewControllerRepresentable。当用户点击“Google 搜索”时,UIViewControllerRepresentable成功调用model.startCamera()。但是,模型的这种更改并未反映在WebviewContainer 中。这是为什么?这不是@ObservedObject的全部目的吗?

krj*_*rjw 5

Model在提供的代码中添加了 a ,该代码在startCamera()调用函数时更新。@Published变量应该在 UI 线程上更新,因为在大多数情况下它们会更改 UI 状态,从而导致 UI 更新。

这是完整的示例:

import SwiftUI
import Foundation
import WebKit

class Coordinator: NSObject {
    var webview: Webview

    init(_ webview: Webview) {
        self.webview = webview
    }

    @objc func startCamera(sender: WKWebView) {
        webview.startCamera()
    }
}

class EmbeddedWebviewController: UIViewController, WKNavigationDelegate {

    var webview: WKWebView
    //var router: WebviewRouter? = nil

    public var delegate: Coordinator? = nil

    init(coordinator: Coordinator) {
        self.delegate = coordinator
        self.webview = WKWebView()
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder: NSCoder) {
        self.webview = WKWebView()
        super.init(coder: coder)
    }

    public func loadUrl(_ url: URL) {
        webview.load(URLRequest(url: url))
    }

    override func loadView() {
        self.webview.navigationDelegate = self
        view = webview
    }

    func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
        guard let url = (navigationResponse.response as! HTTPURLResponse).url else {
            decisionHandler(.cancel)
            return
        }

        if url.absoluteString.starts(with: "https://www.google.com/search?") {
            decisionHandler(.cancel)
            self.delegate?.startCamera(sender: self.webview)
        }
        else {
            decisionHandler(.allow)
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

class WebviewModel: ObservableObject {
    @Published var loggedOut: Bool = false
    @Published var shouldRedirectToCameraView: Bool = false
    @Published var navbarHidden: Bool = false
    func startCamera() {
        print("Started Camera")
        DispatchQueue.main.async {
            self.shouldRedirectToCameraView.toggle()
        }
    }
}

struct Webview : UIViewControllerRepresentable {
    @ObservedObject var model: WebviewModel

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

    func makeUIViewController(context: Context) -> EmbeddedWebviewController {
        let webViewController = EmbeddedWebviewController(coordinator: context.coordinator)
        webViewController.loadUrl(URL(string:"https://www.google.com")!)

        return webViewController
    }

    func updateUIViewController(_ uiViewController: EmbeddedWebviewController, context: UIViewControllerRepresentableContext<Webview>) {

    }

    func startCamera() {
        model.startCamera()
    }
}

struct LoginView: View {
    var body: some View {
        Text("Login")
    }
}

struct CameraView: View {
    @ObservedObject var model: WebviewModel
    var body: some View {
        Text("CameraView")
    }
}

struct WebviewContainer: View {
    @ObservedObject var model: WebviewModel = WebviewModel()
    var body: some View {
        return NavigationView {
            VStack {
                NavigationLink(destination: LoginView(), isActive: $model.loggedOut) {
                    EmptyView()
                }.isDetailLink(false)
                .navigationBarTitle(Text("Hallo"))
                .navigationBarHidden(self.model.navbarHidden)

                NavigationLink(destination: CameraView(model: self.model), isActive: $model.shouldRedirectToCameraView) {
                    EmptyView()
                }
                .navigationBarTitle(Text(""))
                .navigationBarHidden(self.model.navbarHidden)
                Webview(model: self.model)
            }
        }
    }
}


struct ContentView: View {

    var body: some View {
        WebviewContainer()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Run Code Online (Sandbox Code Playgroud)

演示

我希望这有帮助。


Sch*_*ken 0

好吧,现在我在进行了一次相当紧张的调试后想通了:

1.) 我在这篇文章中提供的代码确实有效。这只是在我周围的代码上下文中存在问题

2.)到目前为止唯一提供的答案并不能解决任何问题。只是因为它已经在工作并且与未在主线程上执行的代码无关(尽管我当然同意,这应该针对影响 UI 的操作完成)。

3.) 就我而言,问题出在导致WebviewContainer 的视图中。在这个视图中,我有一个模型正在 API 调用中更改其值。如果成功,它会决定重定向到WebviewContainer,如果失败则不会。到目前为止,一切都很好。但是,我在if中进行模型更改,应该在else中阻止它。我错过了其他的事情,所以有一瞬间它在做正确的事情,然后又切换回来。这很难调试,因为当我观看模型时,一切都发现了。唯一奇怪的是模型的构造函数被调用了两次。

很抱歉,在这种情况下,我无法对给定的答案给予赏金(您将因投入的时间而获得赞成票。

非常感谢!学习:下次尝试尽可能地隔离问题,以减少应用程序代码其余部分的副作用。