简体   繁体   English

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

[英]WKWebView in SwiftUI - How do I switch the view when user interacts with the website?

My situation is the following: I have a SwiftUI application and want to display a WebView.我的情况如下:我有一个 SwiftUI 应用程序并想显示一个 WebView。 When the user taps a certain button in that WebView I want the user to be redirected to the next (SwiftUI) view.当用户点击 WebView 中的某个按钮时,我希望将用户重定向到下一个(SwiftUI)视图。 I use a UIViewRepresentable as this seems to be the current way to go for showing a WebView in SwiftUI because it's the bridge to UIKit. I use a UIViewRepresentable as this seems to be the current way to go for showing a WebView in SwiftUI because it's the bridge to UIKit. The problem is: UIViewRepresentable has no body .问题是: UIViewRepresentable 没有body So where do I tell the view to switch over?那么我在哪里告诉视图切换呢? In usual SwiftUI views I would have a model which I'd update and then react on the model change in the body.在通常的 SwiftUI 视图中,我会有一个 model ,我会对其进行更新,然后对身体中的 model 变化做出反应。

I set up an example in which https://www.google.com is rendered in the WebView.我设置了一个示例,其中https://www.google.com在 WebView 中呈现。 When the user sends a search query the coordinator is called which calls a function of the UIViewRepresentable:当用户发送搜索查询时,将调用协调器,该协调器调用 UIViewRepresentable 的 function:

View - This is the view that should react on the model changes by displaying another view (implemented with NavigationLinks )视图- 这是应该通过显示另一个视图(通过NavigationLinks实现)对 model 更改做出反应的视图

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

UIViewControllerRepresentable - This is necessary in order to use the WKWebview in SwiftUI context 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()
    }
}

UIViewController - The WKNavigationDelegate that reacts on the click on "Google Search" and calls the coordinator 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()
    }
}

Coordinator - Bridge between WKNavigationDelegate and UIViewRepresentable 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()
    }
}

UPDATE更新

I now have a View with a model ( @ObservedObject ).我现在有一个带有 model ( @ObservedObject ) 的视图。 This model is given to the UIViewControllerRepresentable .这个 model 被提供给UIViewControllerRepresentable When the user clicks "Google Search", UIViewControllerRepresentable successfully calls model.startCamera() .当用户点击“谷歌搜索”时, UIViewControllerRepresentable成功调用model.startCamera() However, this change of the model is not reflected in the WebviewContainer .但是,model 的这种变化并没有反映在WebviewContainer中。 Why is that?这是为什么? Isn't that the whole purpose of @ObservedObject s?这不是@ObservedObject的全部目的吗?

I added a Model to the provided code, which is updated when the startCamera() function is called.我在提供的代码中添加了Model ,该代码在startCamera() function 时更新。 @Published variables should be updated on the UI thread since in most cases they change UI state which causes the UI to update. @Published变量应该在 UI 线程上更新,因为在大多数情况下,它们会更改 UI state,从而导致 UI 更新。

Here is the full example:这是完整的示例:

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

演示

I hope this helps.我希望这有帮助。

Okay now, I figured it out after having a quite intense debug session:好的,我在进行了非常激烈的调试 session 之后发现了它:

1.) The code I've presented in this post does indeed work. 1.) 我在这篇文章中提供的代码确实有效。 It was only problematic in the context of my surrounding code .这只是在我周围的代码的上下文中存在问题

2.) The only provided answer so far does not fix anything . 2.)到目前为止唯一提供的答案没有解决任何问题 Just because it's already working and has nothing to do with code not being executed on the main thread (although I certainly agree, that this should be done for actions that affect the UI).仅仅因为它已经在工作并且与不在主线程上执行的代码无关(尽管我当然同意,这应该针对影响 UI 的操作执行)。

3.) In my case the problem was in the view that leads to WebviewContainer . 3.)在我的情况下,问题出在导致WebviewContainer的视图中。 In that view I had a model that was changing its values in an API call.在那个视图中,我有一个 model 在 API 调用中更改其值。 On success it decides to redirect to WebviewContainer in case of failure it doesn't.成功时它决定重定向到WebviewContainer以防失败。 So far so good.到目前为止,一切都很好。 However, I was doing the model change in the if and should have prevented it in else .但是,我在if中进行了 model 更改,并且应该在else中阻止它。 I was missing the else so for a blink of a second it was doing the right thing and then switching back.我错过了其他,所以有一瞬间它正在做正确的事情,然后又切换回来。 This was hard to debug because when I watched the model, everything was finde.这很难调试,因为当我观看 model 时,一切都已找到。 The only thing that was strange was that the constructor of the model was called twice.唯一奇怪的是 model 的构造函数被调用了两次。

I am sorry that in this case I can not give the bounty to the given answer (you will get an upvote for the time invested.很抱歉,在这种情况下,我无法为给定的答案提供赏金(您将获得对所投入时间的支持。

Thank you very much: Learning.非常感谢:学习。 next time try to isolate the issue as much as I can in order to decrease the side effects of the rest of my application code.下次尝试尽可能多地隔离问题,以减少我的应用程序代码的 rest 的副作用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 在 SwiftUI 中使用 WKWebView 时如何拦截链接导航? - How do I intercept link navigation when using WKWebView in SwiftUI? 计时器结束时如何切换视图 SwiftUI - How do I switch views when the timer ends SwiftUI 当用户与ios中的滑块交互时,滚动视图不应滚动 - Scroll view should not scroll when user interacts with slider in ios 当用户进行交互时,如何使摄像机停止动画到更新位置? - How can I make the camera stop animating to updatedLocation, when user interacts? 如何阻止 SwiftUI 重新加载我通过 UIViewRepresentable 使用的 WKWebView 页面? - How do I stop SwiftUI from reloading WKWebView page that I am using via UIViewRepresentable? 如何向 SwiftUI 视图添加操作? - How do I add an action to a SwiftUI view? 如何刷新 SwiftUI 中的视图 - How do I refresh View in SwiftUI 如何使用 SwiftUI 更改根视图 - How do I change the root view with SwiftUI 当环境对象更新导致同一视图重新加载时,如何导航到另一个 SwiftUI 视图 - How do I navigate to another SwiftUI View when an Environment Object update is causing the same view to reload 用户在 Firebase 上使用 Google 登录后,如何在 swiftUI 中重新呈现我的视图? - How do I rerender my view in swiftUI after a user logged in with Google on Firebase?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM