0

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:

  • if you open it on Safari and click on the "SEARCH" button ==> it correctly displays the results page.
  • if you open it on a WKWebView and click on the "SEARCH" button ==> it incorrectly displays a blank screen.

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?

Daniele B
  • 19,801
  • 29
  • 115
  • 173

1 Answers1

1

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.

HangarRash
  • 7,314
  • 5
  • 5
  • 32
  • Great, it works! The solution makes a lot of sense, it's a matter of intercepting the hash change and then loading such URL. I am going to award you a 50 bounty as soon as it becomes available on Monday. – Daniele B Mar 18 '23 at 11:32
  • Instead of `observeValue`, you can actually use `webview.observe`, which doesn't require to be deregistered: https://stackoverflow.com/a/60177070/913867 You might want to update the answer. – Daniele B Mar 18 '23 at 11:40