Notes on listening to location changes in WKWebView
WKNavigationDelegate
is useful for tracking when user navigates to a different URL with functions such as webView(_:,decidePolicyFor:,decisionHandler:)
. But if the site uses HTML History API to the location (common with React and friends), it doesn’t pick it up. Here’s how to support it:
1. This JavaScript to dispatch a message to the WKWebView
instance’s message handler:
// So we can detect when sites use History API to generate the page location. Especially common with React and similar frameworks
;(function() {
var pushState = history.pushState;
var replaceState = history.replaceState;
history.pushState = function() {
pushState.apply(history, arguments);
window.dispatchEvent(new Event('locationchange'));
};
history.replaceState = function() {
replaceState.apply(history, arguments);
window.dispatchEvent(new Event('locationchange'));
};
window.addEventListener('popstate', function() {
window.dispatchEvent(new Event('locationchange'))
});
})();
window.addEventListener('locationchange', function(){
webkit.messageHandlers.locationChanged.postMessage(window.location.href)
})
2. Inject the JavaScript above is to install a user script:
let webViewConfig = WKWebViewConfiguration()
let userScript = WKUserScript(source: js, injectionTime: .atDocumentStart, forMainFrameOnly: false)
webViewConfig.userContentController.addUserScript(userScript)
//Install handler (below)
let webView = WKWebView(frame: .zero, configuration: config)
3. Specify the message handler:
webViewConfig.userContentController.add(messageHandler, name: "locationChanged")
4. Handle the message, getting the URL:
extension SomeClass: WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
//get the URL from `message` or just from `webView.url`
}
}
Thanks to the suggestion from @hishnash.