WebView UIViewRepresentable navigationDelegate not getting called

I have a WKWebView wrapped in a UIViewRepresentable. I want to modify certain URLs during page navigation, but the code for the delegate never gets called...

import SwiftUI
import WebKit

struct SwiftUIWebView: UIViewRepresentable {
	
	let url: URL


	func makeUIView(context: Context) -> WKWebView {
		return WKWebView()
	}

	func updateUIView(_ webView: WKWebView, context: Context) {
		let request = URLRequest(url: url)
		webView.load(request)
	}

	func makeCoordinator() -> Coordinator {
		Coordinator()
	}
}

extension SwiftUIWebView {

	@MainActor class Coordinator: NSObject, WKNavigationDelegate, WKUIDelegate {

		func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction) async -> WKNavigationActionPolicy {
			print("Delegate called...")
			webView.uiDelegate = self
			webView.navigationDelegate = self
			if let url = navigationAction.request.url, url.host != "somedomain.com", await UIApplication.shared.open(url) {
				return .cancel
			} else {
				return .allow
			}
		}
	}
}

I never see the text in the console, and the behavior is not what I expect. What am I missing? 🤔

Answered by Shotster in 758286022

Ok, got it sussed. The code is simpler, and the delegate method now fires with each nav request, allowing me to define different behavior for internal vs. external links. Here's the working code:

import SwiftUI
import WebKit

struct SwiftUIWebView: UIViewRepresentable {

	let url: URL

	func makeUIView(context: Context) -> WKWebView {
		print("makeUIView called...")
		let webView = WKWebView()
		webView.navigationDelegate = context.coordinator
		return webView
	}

	func updateUIView(_ webView: WKWebView, context: Context) {
		print("updateUIView called...")
		let request = URLRequest(url: url)
		webView.load(request)
	}

	func makeCoordinator() -> Coordinator {
		print("makeCoordinator called...")
		let coord = Coordinator()
		return coord
	}

	@MainActor class Coordinator: NSObject, WKNavigationDelegate {
		func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction) async -> WKNavigationActionPolicy {
			print("Delegate called...")
			if let frame = navigationAction.targetFrame, frame.isMainFrame {
				print("Target is main frame...")
				return .allow
			} else {
				print("Target is external...")
				return .cancel
			}
		}
	}
}

The webView.navigationDelegate = context.coordinator line was absolutely key, and the following article...

www.hackingwithswift.com/books/ios-swiftui/using-coordinators-to-manage-swiftui-view-controllers

...was a big help in getting the proper incantation for setting the navigationDelegate.

Thanks again, @BabyJ, for pointing me in the right direction!

You are setting the web view's navigationDelegate property from within one of the delegate's own methods. The delegate can't call that function because it hasn't been assigned to and doesn't know your implementation exists.

Assign your delegates when you create the view, like in the makeUIView(context:) method.

func makeUIView(context: Context) -> WKWebView {
    let webView = WKWebView()
    webView.uiDelegate = context.coordinator
    webView.navigationDelegate = context.coordinator

    return webView
}

Now the delegate knows the functions to call and you should see your print statement appear in the console.

Thanks @BabyJ, but unfortunately, that doesn't work b/c "self" in this context is a NSObject and can't be assigned to a property of type WKNavigationDelegate.

I've refactored from a struct to a class, and the compiler's not complaining, but it's still not working. I feel I'm grasping at straws at this point. Any other insights would be appreciated. Here's what I've got now...

final class SwiftUIWebView: NSObject, WKUIDelegate, UIViewRepresentable, WKNavigationDelegate {

	let url: URL

	override init() {
		self.url = URL(string: "https://mydomain.com/page")!
	}

	func makeUIView(context: Context) -> WKWebView {
		let webView = WKWebView()

		webView.navigationDelegate = self
		webView.uiDelegate = self

		return webView
	}

	func updateUIView(_ webView: WKWebView, context: Context) {
		let request = URLRequest(url: url)
		webView.load(request)
	}

	func makeCoordinator() -> Coordinator {
		print("Coordinator called")
		return Coordinator()
	}

	@MainActor class Coordinator: NSObject, WKNavigationDelegate, WKUIDelegate {
		func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction) async -> WKNavigationActionPolicy {
			print("Delegate called...")
			if let url = navigationAction.request.url, url.host != "mydomain.com", await UIApplication.shared.open(url) {
				return .cancel
			} else {
				return .allow
			}
		}
	}
}
Accepted Answer

Ok, got it sussed. The code is simpler, and the delegate method now fires with each nav request, allowing me to define different behavior for internal vs. external links. Here's the working code:

import SwiftUI
import WebKit

struct SwiftUIWebView: UIViewRepresentable {

	let url: URL

	func makeUIView(context: Context) -> WKWebView {
		print("makeUIView called...")
		let webView = WKWebView()
		webView.navigationDelegate = context.coordinator
		return webView
	}

	func updateUIView(_ webView: WKWebView, context: Context) {
		print("updateUIView called...")
		let request = URLRequest(url: url)
		webView.load(request)
	}

	func makeCoordinator() -> Coordinator {
		print("makeCoordinator called...")
		let coord = Coordinator()
		return coord
	}

	@MainActor class Coordinator: NSObject, WKNavigationDelegate {
		func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction) async -> WKNavigationActionPolicy {
			print("Delegate called...")
			if let frame = navigationAction.targetFrame, frame.isMainFrame {
				print("Target is main frame...")
				return .allow
			} else {
				print("Target is external...")
				return .cancel
			}
		}
	}
}

The webView.navigationDelegate = context.coordinator line was absolutely key, and the following article...

www.hackingwithswift.com/books/ios-swiftui/using-coordinators-to-manage-swiftui-view-controllers

...was a big help in getting the proper incantation for setting the navigationDelegate.

Thanks again, @BabyJ, for pointing me in the right direction!

WebView UIViewRepresentable navigationDelegate not getting called
 
 
Q