SwiftUI 中的 WKWebView - 用户与网站交互时如何切换视图?
Posted
技术标签:
【中文标题】SwiftUI 中的 WKWebView - 用户与网站交互时如何切换视图?【英文标题】:WKWebView in SwiftUI - How do I switch the view when user interacts with the website? 【发布时间】:2019-11-19 08:22:29 【问题描述】:我的情况如下:我有一个 SwiftUI 应用程序并且想要显示一个 WebView。当用户点击该 WebView 中的某个按钮时,我希望将用户重定向到下一个(SwiftUI)视图。 我使用 UIViewRepresentable,因为这似乎是在 SwiftUI 中显示 WebView 的当前方式,因为它是通往 UIKit 的桥梁。 问题是:UIViewRepresentable 没有body。那么我在哪里告诉视图切换呢?在通常的 SwiftUI 视图中,我将有一个模型,我会对其进行更新,然后对主体中的模型更改做出反应。
我设置了一个示例,其中https://www.google.com 在 WebView 中呈现。当用户发送搜索查询时,会调用协调器,该协调器会调用 UIViewRepresentable 的函数:
View - 这是应该通过显示另一个视图来对模型更改做出反应的视图(使用 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)
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 - 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 - WKNavigationDelegate 和 UIViewRepresentable
之间的桥梁import Foundation
import WebKit
class Coordinator: NSObject
var webview: Webview
init(_ webview: Webview)
self.webview = webview
@objc func startCamera(sender: WKWebView)
webview.startCamera()
更新
我现在有一个带有模型的视图 (@ObservedObject)。该模型被赋予 UIViewControllerRepresentable。当用户点击“谷歌搜索”时,UIViewControllerRepresentable成功调用model.startCamera()。但是,模型的这种变化并没有体现在 WebviewContainer 中。这是为什么?这不是 @ObservedObject 的全部目的吗?
【问题讨论】:
您需要将一些可观察对象(例如,通过@ObservedObject
或@EnvironmentObject
)传递到您的Webview 并在doSomething
中更改一些@Published
属性,具体取决于您的ContentView 显示的值(或导航到)其他一些视图。
你能分享剩下的代码吗?我无法拼凑出一个可行的例子
缺少哪些代码部分?我能提供什么帮助?
【参考方案1】:
我在提供的代码中添加了一个Model
,它会在调用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()
我希望这会有所帮助。
【讨论】:
所以你所做的唯一改变是将变量的修改包装在一个包装器中,告诉它的主体在主线程上执行?很抱歉,但这对我不起作用:(。我在控制台上看到打印(“已启动的相机”),但视图不会切换。实际上,如果这是必要的,我会感到惊讶。DispatchQueue 是一个类属于 UIKit,而要呈现的 View 是用 SwiftUI 制作的。但也许我错过了你所做的任何其他更改?你能详细说明一下吗?:) 谢谢!@Published
变量的更改需要在 UI 线程上执行,因为它们会操纵视图。
@Schnodderbalken 我添加了完整的示例......当我搜索某些内容并按下搜索图标时,它会移动到 CameraView
我自己写了一个答案!感谢您的努力。【参考方案2】:
好的,我在进行了相当激烈的调试后才弄清楚:
1.) 我在这篇文章中提供的代码确实有效。 这只是在我周围的代码上下文中存在问题。
2.) 迄今为止唯一提供的答案不能解决任何问题。仅仅因为它已经在工作并且与不在主线程上执行的代码无关(尽管我当然同意,这应该针对影响 UI 的操作执行)。
3.) 就我而言,问题出在导致 WebviewContainer 的视图中。在那个视图中,我有一个模型在 API 调用中改变了它的值。成功时它决定重定向到 WebviewContainer 以防失败。到现在为止还挺好。但是,我在 if 中进行了模型更改,并且应该在 else 中阻止它。我错过了其他,所以有一瞬间它正在做正确的事情,然后又切换回来。这很难调试,因为当我查看模型时,一切都已找到。唯一奇怪的是模型的构造函数被调用了两次。
很抱歉,在这种情况下,我无法为给定的答案提供赏金(您将获得对所投入时间的支持。
非常感谢!学习:下次尝试尽可能多地隔离问题,以减少其余应用程序代码的副作用。
【讨论】:
以上是关于SwiftUI 中的 WKWebView - 用户与网站交互时如何切换视图?的主要内容,如果未能解决你的问题,请参考以下文章
iOS SwiftUI App Clip WKWebView 和 SFSafariViewController 不起作用
SwiftUI:WKWebView 在 macOS 上截断页面内容(NSViewRepresentable)
如何在 SwiftUI 中观察 WKWebView 的加载进度?
SwiftUI:使用 WkWebView 突出显示具有不同颜色的文本
SwiftUI - WKWebView 的 iOS 13 UIViewRepresentable 获取线程 1:EXC_BREAKPOINT 崩溃