SwiftUI coordinator not updating the containing view's property - swift

So I've wrapped WKWebView in an UIViewRepresentable and built a coordinator in order to access its navigation delegate's functions. In the webView(_:didFinish:) function I am trying to update the view's didFinishLoading variable. If I print right after assigning, it prints true - the expected behavior. But, in the parent view, when I call the getHTML function, it prints false - even if I wait until the WKWebView is fully loaded.
Here is the code:
import SwiftUI
import WebKit
struct WebView: UIViewRepresentable {
#Binding var link: String
init(link: Binding<String>) {
self._link = link
}
private var didFinishLoading: Bool = false
let webView = WKWebView()
func makeUIView(context: UIViewRepresentableContext<WebView>) -> WKWebView {
self.webView.load(URLRequest(url: URL(string: self.link)!))
self.webView.navigationDelegate = context.coordinator
return self.webView
}
func updateUIView(_ uiView: WKWebView, context: UIViewRepresentableContext<WebView>) {
return
}
class Coordinator: NSObject, WKNavigationDelegate {
private var webView: WebView
init(_ webView: WebView) {
self.webView = webView
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
print("WebView: navigation finished")
self.webView.didFinishLoading = true
}
}
func makeCoordinator() -> WebView.Coordinator {
Coordinator(self)
}
func getHTML(completionHandler: #escaping (Any?) -> ()) {
print(self.didFinishLoading)
if (self.didFinishLoading) {
self.webView.evaluateJavaScript(
"""
document.documentElement.outerHTML.toString()
"""
) { html, error in
if error != nil {
print("WebView error: \(error!)")
completionHandler(nil)
} else {
completionHandler(html)
}
}
}
}
}
struct WebView_Previews: PreviewProvider {
#State static var link = "https://apple.com"
static var previews: some View {
WebView(link: $link)
}
}

Here is your code, a bit modified for demo, with used view model instance of ObservableObject holding your loading state.
import SwiftUI
import WebKit
import Combine
class WebViewModel: ObservableObject {
#Published var link: String
#Published var didFinishLoading: Bool = false
init (link: String) {
self.link = link
}
}
struct WebView: UIViewRepresentable {
#ObservedObject var viewModel: WebViewModel
let webView = WKWebView()
func makeUIView(context: UIViewRepresentableContext<WebView>) -> WKWebView {
self.webView.navigationDelegate = context.coordinator
if let url = URL(string: viewModel.link) {
self.webView.load(URLRequest(url: url))
}
return self.webView
}
func updateUIView(_ uiView: WKWebView, context: UIViewRepresentableContext<WebView>) {
return
}
class Coordinator: NSObject, WKNavigationDelegate {
private var viewModel: WebViewModel
init(_ viewModel: WebViewModel) {
self.viewModel = viewModel
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
print("WebView: navigation finished")
self.viewModel.didFinishLoading = true
}
}
func makeCoordinator() -> WebView.Coordinator {
Coordinator(viewModel)
}
}
struct WebViewContentView: View {
#ObservedObject var model = WebViewModel(link: "https://apple.com")
var body: some View {
VStack {
TextField("", text: $model.link)
WebView(viewModel: model)
if model.didFinishLoading {
Text("Finished loading")
.foregroundColor(Color.red)
}
}
}
}
struct WebView_Previews: PreviewProvider {
static var previews: some View {
WebViewContentView()
}
}

Related

WKWebView not receiving configuration from previous WebView

I'm trying to receive the configuration that , to my understanding, should have been transmitted from a the previous website. The example below is a website that has a simple login button, when pressed, it opens a new tab (which I want to open in the same view). There you'd login and after that you would be brought back to the previous site.
The website can also be visited in a normal browser website
Its an open source project found here Github
I have the following code:
ContentView
import SwiftUI
import UIKit
import WebKit
struct ContentView: View {
var webView: WKWebView
init() {
let userContentController = WKUserContentController() //CustomContentController()
let webViewConfiguration = WKWebViewConfiguration()
webViewConfiguration.userContentController = userContentController
self.webView = WKWebView(frame: .zero, configuration: webViewConfiguration)
self.webView.configuration.limitsNavigationsToAppBoundDomains = true
self.webView.configuration.preferences.javaScriptCanOpenWindowsAutomatically = true
}
var body: some View {
WebviewNew(web: webView, req: URLRequest(url: URL(string: "https://vasb2-4yaaa-aaaab-qadoa-cai.ic0.app/")!))
}
}
WebViewNew
struct WebviewNew : UIViewRepresentable {
let request: URLRequest
var webview: WKWebView?
init(web: WKWebView?, req: URLRequest) {
self.webview = web ?? WKWebView()
self.request = req
}
class Coordinator: NSObject, WKUIDelegate, WKNavigationDelegate {
var parent: WebviewNew
init(_ parent: WebviewNew) {
self.parent = parent
}
func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
if navigationAction.targetFrame == nil {
let newWebView = WKWebView(frame: webView.frame, configuration: configuration)
newWebView.uiDelegate = self
newWebView.load(navigationAction.request)
//
return newWebView
// webView.load(navigationAction.request)
}
return nil
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: Context) -> WKWebView {
return webview!
}
func updateUIView(_ uiView: WKWebView, context: Context) {
uiView.uiDelegate = context.coordinator
uiView.load(request)
}
func goBack(){
webview?.goBack()
}
func goForward(){
webview?.goForward()
}
func reload(){
webview?.reload()
}
}
This solution does somehow not work. When clicking the Login Button on the website inside the WKWebView nothing happens. When setting a breakpoint in func webView one can see that it gets passed but somehow nothing happens.
When altering the WebViewNew like this:
struct WebviewNew : UIViewRepresentable {
let request: URLRequest
var webview: WKWebView?
init(web: WKWebView?, req: URLRequest) {
self.webview = web ?? WKWebView()
self.request = req
}
class Coordinator: NSObject, WKUIDelegate, WKNavigationDelegate {
var parent: WebviewNew
init(_ parent: WebviewNew) {
self.parent = parent
}
func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
if navigationAction.targetFrame == nil {
// Changed HERE
webView.load(navigationAction.request)
}
return nil
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: Context) -> WKWebView {
return webview!
}
func updateUIView(_ uiView: WKWebView, context: Context) {
uiView.uiDelegate = context.coordinator
uiView.load(request)
}
func goBack(){
webview?.goBack()
}
func goForward(){
webview?.goForward()
}
func reload(){
webview?.reload()
}
}
With a change in func webView it opens the login page but it seems to be missing the context as the login does not know who made the request.
In the open source website the login button can be found here
Why is my solution not working and what could make it work?

Redirect link in swift ui

I am working on a web application for iOS. I would like that when the user clicks on the link sign in the link is redirected. I need this because I need to paste the token created for the user behind it. For now, the redirect link is nu.nl. I have this now, but unfortunately it doesn't work. I am not familiar with the language swift ui. Thanks in advance for your help :)
Contentview
import SwiftUI
import Foundation
import WebKit
struct ContentView: View {
var body: some View {
WebView(url: URL(string:"https://ferocity.bytemountains.com/client")!).frame(maxWidth: .infinity, maxHeight: .infinity).edgesIgnoringSafeArea(.all)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Webview
import Foundation
import SwiftUI
import WebKit
struct WebView: UIViewRepresentable {
let url: URL
let webView = WKWebView()
func makeUIView(context: Context) -> some UIView {
let request = URLRequest(url: url)
webView.load(request)
return webView
}
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Swift.Void) {
if(navigationAction.navigationType == .other) {
if navigationAction.request.url != nil {
//do what you need with url
webView.load(URLRequest(url: URL(string: "https://nu.nl/")!))
}
decisionHandler(.cancel)
return
}
decisionHandler(.allow)
}
func updateUIView(_ uiView: UIViewType, context: Context) {
}
}
enter image description here
You will have to use Coordinator and then need to conform the WKNavigationDelegate to call its delegate methods.
struct WebView : UIViewRepresentable {
let url: URL
var webView = WKWebView()
func makeUIView(context: Context) -> WKWebView {
let request = URLRequest(url: url)
webView.load(request)
return webView
}
func updateUIView(_ uiView: WKWebView, context: Context) {
uiView.navigationDelegate = context.coordinator
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
//Conform to WKNavigationDelegate protocol here and declare its delegate
class Coordinator: NSObject, WKNavigationDelegate {
var parent: WebView
init(_ parent: WebView) {
self.parent = parent
}
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
//Navigate to other URL if user clicks on the sign in link
if navigationAction.request.url?.absoluteString == "https://sso.bytemountains.com/?clientId=2" {
webView.load(URLRequest(url: URL(string: "https://nu.nl/")!))
}
decisionHandler(.allow)
}
}
}

Connecting UIKit with SwiftUI, why did I lose my NavBar?

So when I use the old UIKit Lifecycle with SceneDelegate and AppDelegate I get the desired result. But when I use the new App life cycle I can't figure out to get the NavBar back..
import SwiftUI
struct ContentView: View {
var body: some View {
DiffableContainer()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
DiffableContainer()
.ignoresSafeArea()
}
}
struct DiffableContainer: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> UIViewController {
return UINavigationController(rootViewController: DiffableTableVC())
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
}
}
class DiffableTableVC: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.backgroundColor = .green
navigationItem.title = "Test"
title = "Hello"
}
}
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
print("This is set up and running")
return true
}
}
Here is the lifecycle that I am using now:
#main
struct ContactsApp: App {
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}

Inject javascript into SwiftUI WebKit

How would I set the text size of a SwiftUI WebKit View to a #State variable? I have this code that allows me to use WebViews in SwiftUI
import SwiftUI
import WebKit
struct WebView : UIViewRepresentable {
var url : URL;
func makeUIView(context: Context) -> WKWebView {
return WKWebView();
}
func updateUIView(_ uiView: UIViewType, context: Context) {
uiView.loadFileURL(url, allowingReadAccessTo: url)
}
}
I saw this and tried to replicate it,
class Coordinator : NSObject {
func webView(_ webView: WKWebView, js : String) {
webView.evaluateJavaScript(js)
}
}
But I wasn't able to get it to work.
Thanks in advance.
You can try to do this in navigation delegate, after loading did finish, as shown below:
class Coordinator : NSObject, WKNavigationDelegate {
// ... any other code
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
webView.evaluateJavaScript(js, completionHandler: { (value, error) in
// .. do anything needed with result, if any
})
}
}
struct WebView : UIViewRepresentable {
var url : URL;
func makeUIView(context: Context) -> WKWebView {
let webview = WKWebView()
webview.navigationDelegate = context.coordinator
return webview
}
func makeCoordinator() -> Coordinator {
Coordinator()
}
func updateUIView(_ uiView: UIViewType, context: Context) {
uiView.loadFileURL(url, allowingReadAccessTo: url)
}
}

How to implement WKUIDelegate into SwiftUI WKWebView?

I'm creating a web app on Xcode v11 and having a trouble implementing WKUIDelegate to display Javascript alert and confirm properly on the web app.
I got a very simple webview app with below code on ContentView.swift but not sure how to implement WKUIDelegate properly with this code.
import SwiftUI
import WebKit
struct Webview : UIViewRepresentable {
let request: URLRequest
var webview: WKWebView?
init(web: WKWebView?, req: URLRequest) {
self.webview = WKWebView()
self.request = req
}
func makeUIView(context: Context) -> WKWebView {
return webview!
}
func updateUIView(_ uiView: WKWebView, context: Context) {
uiView.load(request)
}
func goBack(){
webview?.goBack()
}
func goForward(){
webview?.goForward()
}
func reload(){
webview?.reload()
}
}
struct ContentView: View {
let webview = Webview(web: nil, req: URLRequest(url: URL(string: "https://google.com")!))
var body: some View {
VStack {
webview
HStack() {
Button(action: {
self.webview.goBack()
}){
Image(systemName: "chevron.left")
}.padding(32)
Button(action: {
self.webview.reload()
}){
Image(systemName: "arrow.clockwise")
}.padding(32)
Button(action: {
self.webview.goForward()
}){
Image(systemName: "chevron.right")
}.padding(32)
}.frame(height: 32)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
What is the best way to implement WKUIDelegate into this code? So, it will display Javascript Alert and Confirm properly.
You need to use Coordinator and then conform to WKUIDelegate:
class Coordinator: NSObject, WKUIDelegate {
var parent: Webview
init(_ parent: Webview) {
self.parent = parent
}
// Delegate methods go here
func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: #escaping () -> Void) {
// alert functionality goes here
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
Then ensure your updateUIView(..) sets the uiDelegate to the context.coordinator:
func updateUIView(_ uiView: WKWebView, context: Context) {
uiView.uiDelegate = context.coordinator
[...]
}
If you want to conform to WKNavigationDelegate then conform to it and set the navigationDelegate to the context.coordinator as well.
Full code here:
import SwiftUI
import WebKit
struct Webview : UIViewRepresentable {
let request: URLRequest
var webview: WKWebView?
init(web: WKWebView?, req: URLRequest) {
self.webview = WKWebView()
self.request = req
}
class Coordinator: NSObject, WKUIDelegate {
var parent: Webview
init(_ parent: Webview) {
self.parent = parent
}
// Delegate methods go here
func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: #escaping () -> Void) {
// alert functionality goes here
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: Context) -> WKWebView {
return webview!
}
func updateUIView(_ uiView: WKWebView, context: Context) {
uiView.uiDelegate = context.coordinator
uiView.load(request)
}
func goBack(){
webview?.goBack()
}
func goForward(){
webview?.goForward()
}
func reload(){
webview?.reload()
}
}