Issue with observing WKWebView URL changes via JavaScript events

风格不统一 提交于 2020-03-20 14:54:08

问题


I have a WKWebView that presents a Laravel web application. I currently have my ViewController setup to catch URL changes – and it catches most of them. However, there are a small number of URL changes that are made via JavaScript events that WKWebView seems to ignore.

I have tried all of the following solutions and implemented them in the code below:

  • How to detect hash changes in WKWebView?
  • How can I detect when url of amp page changed with WKWebview
  • WKWebView function for detecting if the URL has changed
  • How can I detect when url of amp page changed with WKWebview
  • using KVO to observe WKWebView's URL property not work in iOS 10

An interesting note before I present the code: it is able to detect simple #hash changes, but is unable to detect a JavaScript event action that loads from page 1 to page 2 (this is the URL change I'm attempting to catch):

https://myweb.com/dashboardhttps://myweb.com/dashboard/projects/new

ViewController.swift

import UIKit
import WebKit

class ViewController: UIViewController, WKUIDelegate, WKNavigationDelegate {

    @IBOutlet var webView: WKWebView!

    override func viewDidLoad() {
        super.viewDidLoad()

        webView.navigationDelegate = self
        webView.uiDelegate = self

        webView.addObserver(self, forKeyPath: "URL", options: .new, context: nil)
        webView.addObserver(self, forKeyPath: #keyPath(WKWebView.url), options: .new, context: nil)

        webView.load(URLRequest(url: URL(string: "https://myweb.com/")!))
    }

    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        if object as AnyObject? === webView && keyPath == "URL" {
            print("URL Change 1:", webView.url?.absoluteString ?? "No value provided")
        }
        if keyPath == #keyPath(WKWebView.url) {
            print("URL Change 2:", self.webView.url?.absoluteString ?? "# No value provided")
        }
    }

    func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
        let webURL = webView.url?.absoluteString
        let reqURL = navigationAction.request.url?.absoluteString
        print("webURL:", webURL)
        print("reqURL:", reqURL)
        decisionHandler(.allow)
    }

    func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
        let webURL = webView.url?.absoluteString
        print("webURL:", webURL)
    }
}

There is one single piece of code that seems to fire on the JavaScript event action, and that is keyPath == #keyPath(WKWebView.url).

However, it only seems to fire just that, as well as its resulting print to console: URL Change 2: # No value provided (as a consequence of the nil coalescing operator: self.webView.url?.absoluteString ?? "# No value provided".

Meaning, these 2 lines of observer code are the only things that catch this change, but do not return the value of that change:

webView.addObserver(self, forKeyPath: #keyPath(WKWebView.url), options: .new, context: nil)

if keyPath == #keyPath(WKWebView.url) {
    print("URL Change 2:", self.webView.url?.absoluteString ?? "# No value provided")
}

Any other attempt made to retrieve this changed value, or force unwrap it, results in a crash on launch immediately after didStartProvisionalNavigation is called:

Xcode Error

Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value

Xcode Console Log

Fatal error: Unexpectedly found nil while unwrapping an Optional value: file /Users/me/Apps/MyWeb/iOS/MyWeb/ViewController.swift, line 125
2020-02-11 12:47:55.169765-0500 MyWeb[3066:124221] Fatal error: Unexpectedly found nil while unwrapping an Optional value: file /Users/me/Apps/MyWeb/iOS/MyWeb/ViewController.swift, line 125
(lldb) 

How am I able to output the value of that URL change? Again, it seems to be the only code that fires when I click the JS button to load the page (or at least, so says the console). In Safari, for both iOS and MacOS, the URL change does seem to reflect in the URL Search bar; thus, I don't believe this is an issue having to do with the web-based code (ie. frames, URL spoofing).

However, I cannot confirm this as it is a web-based PHP/JS script.

Edit

It appears that, when first loading https://myweb.com in the WKWebView, the code also does not detect the initial redirect to https://myweb.com/dashboard. Only the two observer lines, mentioned above, seem to detect this redirect.

Edit 2

I am able to get the exact URL value printed to the console with the observerValue function code:

if let key = change?[NSKeyValueChangeKey.newKey] {
    print("URL: \(key)") // url value
}

However, I am unable to cast it as a String, assign the value to a variable or pass it to another function otherwise. Is there any way to set this key as a variable if it .contains("https://")? On launch, the key value = 0, but after that, the value = the desired URL (https://myweb.com/dashboard and https://myweb.com/dashboard/project/new).


This solution uses older KVO methods to achieve the same result as @Rudedog, but with much more baggage (ie. you still need to remove the observer and handle independent key paths). See @Rudedog's answer below for a more modern solution.

Edit 3 (KVO Solution)

I was able to assign the KVO WKWebView.url to a String variable. From there, I was able to pass the String value to a function that then handles each output I'm looking for:

var cURL = ""

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if let key = change?[NSKeyValueChangeKey.newKey] {
        cURL = "\(key)"         // Assign key-value to String
        print("cURL:", cURL)    // Print key-value
        cURLChange(url: cURL)   // Pass key-value to function
    }
}

func cURLChange(url: String) {
    if cURL.contains("/projects/new") {
        print("User launched new project view")
        // Do something
    } else {
        // Do something else
    }
}

回答1:


Before you go any further, I would strongly recommend that you use the modern Swift method of observing:

var webViewURLObserver: NSKeyValueObservation?

override func viewDidLoad() {
  // ...
  webViewURLObserver = webview.observe(\.url, options: .new) { webview, change in 
    print("URL: \(String(describing: change.newValue))"
    // The webview parameter is the webview whose url changed
    // The change parameter is a NSKeyvalueObservedChange
    // n.b.: you don't have to deregister the observation; 
    // this is handled automatically when webViewURLObserver is dealloced.
  }
}

Doing this makes your code much cleaner and easier to reason about while you're debugging the issue.



来源:https://stackoverflow.com/questions/60175052/issue-with-observing-wkwebview-url-changes-via-javascript-events

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!