I am trying to display a web app on an iOS WKWebView
, but hash urls don't seem to work.
However if I open the same web app on iOS Safari, the hash urls work properly. They also work properly on both the Android WebView and Android Chrome.
It's very easy to verify the issue with this URL:
According to this there seems to be some issues with the window.onhashchange
event on WKWebView.
Is there any Swift or injected Javascript code that can fix this issue, so that the web app behaves on the WKWebView the same as on Safari?
I was able to get this to work by using a solution from the linked question and little bit of a hack to determine when to reload the web view with the updated URL.
Here is a little test code that can be copied into an iOS Swift Playground.
import UIKit
import WebKit
class WebVC: UIViewController, WKNavigationDelegate {
var webView: WKWebView!
var pending = false
override func viewDidLoad() {
super.viewDidLoad()
webView = WKWebView()
webView.navigationDelegate = self
webView.addObserver(self, forKeyPath: "URL", options: .new, context: nil)
view.addSubview(webView)
webView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
webView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
webView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
webView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
webView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
])
}
// Code from the linked answer
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
if object as AnyObject? === webView && keyPath == "URL" {
print(webView.url)
if let url = webView.url, pending {
// Probably need to add a sanity check to avoid a reload loop.
// Not sure if that can happen in some corner case or not.
let req = URLRequest(url: url)
webView.load(req)
}
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let req = URLRequest(url: URL(string: "https://www.lefrecce.it/Channels.Website.WEB/#/white-label/MINISITI/?minisitiCallBackId=54276&isRoundTrip=false&lang=en&departureStation=Salerno&arrivalStation=Caserta")!)
webView.load(req)
}
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, preferences: WKWebpagePreferences) async -> (WKNavigationActionPolicy, WKWebpagePreferences) {
print("decidePolicyFor action, prefs: \(preferences)")
pending = true
return (.allow, preferences)
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
print("didFinish \(navigation)")
pending = false
}
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
print("Error: \(error)")
}
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
print("Provisional error: \(error)")
}
}
PlaygroundPage.current.liveView = WebVC()
The whole pending
flag is a first attempt to a solution. It's likely a little fragile but it works in this case. There might be corner cases where it ends up causing a loop of reloads so further testing is needed to be sure.
Code also needs to be added to remove the observer from webView
. Probably in a deinit
for the view controller.