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. When the user taps a certain button in that WebView I want the user to be redirected to the next (SwiftUI) view.
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. 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.
I set up an example in which https://www.google.com is rendered in the WebView. When the user sends a search query the coordinator is called which calls a function of the UIViewRepresentable:
View – This is the view that should react on the model changes by displaying another view (implemented with 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 – This is necessary in order to use the WKWebview in SwiftUI context
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
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
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). This model is given to the UIViewControllerRepresentable. When the user clicks “Google Search”, UIViewControllerRepresentable successfully calls model.startCamera(). However, this change of the model is not reflected in the WebviewContainer. Why is that? Isn’t that the whole purpose of @ObservedObjects?
Source: Ios
I have the same problem.
“`
NavigationLink(destination: … isActive: $…) {
EmptyView()
}
“`
That way seems not work.