SwiftUI 中的 WKWebView - 当用户与网站交互时如何切换视图?
WKWebView in SwiftUI - How do I switch the view when user interacts with the website?
我的情况是这样的:我有一个 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 Search" 时做出反应并调用协调器
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。当用户点击"Google Search"时,UIViewControllerRepresentable成功调用model.startCamera()。但是,模型的这种变化并没有反映在 WebviewContainer 中。这是为什么?这不是 @ObservedObject 的全部目的吗?
我在提供的代码中添加了一个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()
}
}
希望对您有所帮助。
现在好了,我在进行了相当激烈的调试会话后弄明白了:
1.) 我在此 post 中提供的代码确实有效。 它只是在我周围的代码上下文中有问题。
2.) 目前唯一提供的答案没有解决任何问题。仅仅因为它已经在工作并且与未在主线程上执行的代码无关(尽管我当然同意,这应该用于影响 UI 的操作)。
3.) 在我的例子中,问题出在导致 WebviewContainer 的视图中。在该视图中,我有一个模型在 API 调用中更改其值。成功时它决定重定向到 WebviewContainer 以防失败。到目前为止,一切都很好。但是,我在 if 中进行模型更改,应该在 else 中阻止它。我错过了 else 所以眨眼间它正在做正确的事情然后切换回来。这很难调试,因为当我观看模型时,一切都很好。唯一奇怪的是模型的构造函数被调用了两次。
很抱歉,在这种情况下,我无法为给定的答案提供赏金(您将因投入的时间而获得赞成票。
非常感谢!学习:下次尝试尽可能多地隔离问题,以减少其余应用程序代码的副作用。
我的情况是这样的:我有一个 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 Search" 时做出反应并调用协调器
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。当用户点击"Google Search"时,UIViewControllerRepresentable成功调用model.startCamera()。但是,模型的这种变化并没有反映在 WebviewContainer 中。这是为什么?这不是 @ObservedObject 的全部目的吗?
我在提供的代码中添加了一个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()
}
}
希望对您有所帮助。
现在好了,我在进行了相当激烈的调试会话后弄明白了:
1.) 我在此 post 中提供的代码确实有效。 它只是在我周围的代码上下文中有问题。
2.) 目前唯一提供的答案没有解决任何问题。仅仅因为它已经在工作并且与未在主线程上执行的代码无关(尽管我当然同意,这应该用于影响 UI 的操作)。
3.) 在我的例子中,问题出在导致 WebviewContainer 的视图中。在该视图中,我有一个模型在 API 调用中更改其值。成功时它决定重定向到 WebviewContainer 以防失败。到目前为止,一切都很好。但是,我在 if 中进行模型更改,应该在 else 中阻止它。我错过了 else 所以眨眼间它正在做正确的事情然后切换回来。这很难调试,因为当我观看模型时,一切都很好。唯一奇怪的是模型的构造函数被调用了两次。
很抱歉,在这种情况下,我无法为给定的答案提供赏金(您将因投入的时间而获得赞成票。
非常感谢!学习:下次尝试尽可能多地隔离问题,以减少其余应用程序代码的副作用。