Using WebKit Delegates

Hi,

How would I go about using the standard WebKit delegates in SwiftUI, such as:


func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!)
    {
        activityView?.startAnimating()
        self.title = "Loading...."
    }


Here's what I have so far:


import SwiftUI
import UIKit
import Foundation
import WebKit

struct Support: View {
    var body: some View {
        
        NavigationView {
            
        VStack {
                    
        WebView(request: URLRequest(url: URL(string: "https://www.connectingpeoplesoftware.com/iossupport")!))
                   
                       }.navigationBarTitle(Text("Support"))
            
               }
           }

           struct WebView: UIViewRepresentable
           {
               let request: URLRequest
               
               func makeUIView(context: Context) -> WKWebView
               {
                   return WKWebView()
                
                   
               }
               
               func updateUIView(_ uiView: WKWebView, context: Context)
               {
                   
                   uiView.load(request)
               }
           }

struct Support_Previews: PreviewProvider {
    static var previews: some View {
        Support()
    }
}

}


Thanks in advance,

Dan

Accepted Reply

Your UIViewRepresentable has an associated class type called Coordinator which is used to set up these relationships. You need to implement makeCoordinator() in your WebView implementation to return an instance of your own class, and then set that class as the WKWebView's delegate.


Here's a quick example:


import SwiftUI
import WebKit

struct WebView: UIViewRepresentable {
    @Binding var title: String
    var url: URL
    var loadStatusChanged: ((Bool, Error?) -> Void)? = nil

    func makeCoordinator() -> WebView.Coordinator {
        Coordinator(self)
    }

    func makeUIView(context: Context) -> WKWebView {
        let view = WKWebView()
        view.navigationDelegate = context.coordinator
        view.load(URLRequest(url: url))
        return view
    }

    func updateUIView(_ uiView: WKWebView, context: Context) {
        // you can access environment via context.environment here
        // Note that this method will be called A LOT
    }

    func onLoadStatusChanged(perform: ((Bool, Error?) -> Void)?) -> some View {
        var copy = self
        copy.loadStatusChanged = perform
        return copy
    }

    class Coordinator: NSObject, WKNavigationDelegate {
        let parent: WebView

        init(_ parent: WebView) {
            self.parent = parent
        }

        func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
            parent.loadStatusChanged?(true, nil)
        }

        func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
            parent.title = webView.title ?? ""
            parent.loadStatusChanged?(false, nil)
        }

        func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
            parent.loadStatusChanged?(false, error)
        }
    }
}

struct Display: View {
    @State var title: String = ""
    @State var error: Error? = nil

    var body: some View {
        NavigationView {
            WebView(title: $title, url: URL(string: "https://www.apple.com/")!)
                .onLoadStatusChanged { loading, error in
                    if loading {
                        print("Loading started")
                        self.title = "Loading…"
                    }
                    else {
                        print("Done loading.")
                        if let error = error {
                            self.error = error
                            if self.title.isEmpty {
                                self.title = "Error"
                            }
                        }
                        else if self.title.isEmpty {
                            self.title = "Some Place"
                        }
                    }
            }
            .navigationBarTitle(title)
        }
    }
}

struct WebView_Previews: PreviewProvider {
    static var previews: some View {
        Display()
    }
}
  • func makeCoordinator() -> WebView.Coordinator { Coordinator(self) }

    Added this func but class Coordinator is not getting called.

Add a Comment

Replies

Your UIViewRepresentable has an associated class type called Coordinator which is used to set up these relationships. You need to implement makeCoordinator() in your WebView implementation to return an instance of your own class, and then set that class as the WKWebView's delegate.


Here's a quick example:


import SwiftUI
import WebKit

struct WebView: UIViewRepresentable {
    @Binding var title: String
    var url: URL
    var loadStatusChanged: ((Bool, Error?) -> Void)? = nil

    func makeCoordinator() -> WebView.Coordinator {
        Coordinator(self)
    }

    func makeUIView(context: Context) -> WKWebView {
        let view = WKWebView()
        view.navigationDelegate = context.coordinator
        view.load(URLRequest(url: url))
        return view
    }

    func updateUIView(_ uiView: WKWebView, context: Context) {
        // you can access environment via context.environment here
        // Note that this method will be called A LOT
    }

    func onLoadStatusChanged(perform: ((Bool, Error?) -> Void)?) -> some View {
        var copy = self
        copy.loadStatusChanged = perform
        return copy
    }

    class Coordinator: NSObject, WKNavigationDelegate {
        let parent: WebView

        init(_ parent: WebView) {
            self.parent = parent
        }

        func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
            parent.loadStatusChanged?(true, nil)
        }

        func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
            parent.title = webView.title ?? ""
            parent.loadStatusChanged?(false, nil)
        }

        func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
            parent.loadStatusChanged?(false, error)
        }
    }
}

struct Display: View {
    @State var title: String = ""
    @State var error: Error? = nil

    var body: some View {
        NavigationView {
            WebView(title: $title, url: URL(string: "https://www.apple.com/")!)
                .onLoadStatusChanged { loading, error in
                    if loading {
                        print("Loading started")
                        self.title = "Loading…"
                    }
                    else {
                        print("Done loading.")
                        if let error = error {
                            self.error = error
                            if self.title.isEmpty {
                                self.title = "Error"
                            }
                        }
                        else if self.title.isEmpty {
                            self.title = "Some Place"
                        }
                    }
            }
            .navigationBarTitle(title)
        }
    }
}

struct WebView_Previews: PreviewProvider {
    static var previews: some View {
        Display()
    }
}
  • func makeCoordinator() -> WebView.Coordinator { Coordinator(self) }

    Added this func but class Coordinator is not getting called.

Add a Comment

Thanks!

@JimDovey your quick example is very helpful thank you.

Any chance you might be able to explain how to include forward and backward navigation in your example.

In func makeUIView(context: Context) -> WKWebView {} I have attempted to include...

Code Block
view.allowsBackForwardNavigationGestures = true


Using your struct Display I can add buttons without too much trouble immediately beneath .navigationBarTitle...

Code Block
        .toolbar {
            ToolbarItemGroup(placement: .navigation) {
                Button(action: {
// action to navigate back here
                }) {
                    Image(systemName: "chevron.left")
                }
                Button(action: {
// action to navigate forward here
                }) {
                    Image(systemName: "chevron.right")
                }
            }
        }


It is the code that is required to actually communicate between the SwiftUI View and its WebView: UIViewRepresentable that I'm struggling with.