通过 JavaScript 事件观察 WKWebView URL 更改的问题

Posted

技术标签:

【中文标题】通过 JavaScript 事件观察 WKWebView URL 更改的问题【英文标题】:Issue with observing WKWebView URL changes via JavaScript events 【发布时间】:2020-02-11 18:11:19 【问题描述】:

我有一个 WKWebView,它显示了一个 Laravel Web 应用程序。我目前有我的 ViewController 设置来捕捉 URL 变化——它捕捉到了大部分变化。但是,WKWebView 似乎忽略了通过 javascript 事件进行的少量 URL 更改。

我已经尝试了以下所有解决方案,并在下面的代码中实现了它们:

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

在我展示代码之前有一个有趣的说明:它能够检测到简单的#hash 更改,但无法检测从第 1 页加载到第 2 页的 JavaScript 事件操作(这是我正在尝试的 URL 更改抓住):

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)
    

似乎在 JavaScript 事件操作上触发了一段代码,那就是 keyPath == #keyPath(WKWebView.url)

但是,它似乎只是触发了这一点,并将结果打印到控制台:URL Change 2: # No value provided(由于 nil 合并运算符:self.webView.url?.absoluteString ?? "# No value provided"

意思是,这 2 行观察者代码是唯一能捕捉到这个变化的东西,但不返回那个变化的值:

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")

在调用didStartProvisionalNavigation 后,任何其他尝试检索此更改的值或强制解包它都会导致启动时崩溃:

Xcode 错误

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

Xcode 控制台日志

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) 

如何输出该 URL 更改的值?同样,当我单击 JS 按钮加载页面时,它似乎是唯一触发的代码(或者至少,控制台是这样说的)。在 Safari 中,对于 iOS 和 MacOS,URL 更改似乎确实反映在 URL 搜索栏中;因此,我不认为这是与基于 Web 的代码(即框架、URL 欺骗)有关的问题。

但是,我无法确认这是一个基于 Web 的 php/JS 脚本。

编辑

看来,当第一次在 WKWebView 中加载 https://myweb.com 时,代码也没有检测到初始重定向到 https://myweb.com/dashboard。只有上面提到的两条观察线似乎检测到了这种重定向。

编辑 2

我能够使用observerValue函数代码将确切的URL值打印到控制台:

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

但是,我无法将其转换为字符串、将值分配给变量或将其传递给另一个函数。如果它.contains("https://"),有什么方法可以将此键设置为变量?启动时,键值 = 0,但之后,值 = 所需的 URL(https://myweb.com/dashboardhttps://myweb.com/dashboard/project/new)。


此解决方案使用较旧的 KVO 方法来实现与 @Rudedog 相同的结果,但有更多的包袱(即,您仍然需要删除观察者并处理独立的关键路径)。有关更现代的解决方案,请参阅下面的 @Rudedog 答案。

编辑 3(KVO 解决方案)

我能够将 KVO WKWebView.url 分配给 String 变量。从那里,我能够将字符串值传递给一个函数,然后处理我正在寻找的每个输出:

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】:

在你继续之前,我强烈建议你使用现代的 Swift 观察方法:

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.
  

这样做可以让您的代码在调试问题时更加简洁和易于推理。

【讨论】:

感谢您的解决方案!我不知道有一种“现代”的观察方式。您的代码运行良好,可以按预期检测 URL 更改。有没有办法让它调用一个函数,即。 urlDidChange(),价值发生变化? 没关系,我能够完成它:webViewURLObserver = webView.observe(\.url, options: .new) webView, change in let url = "\(String(describing: change.newValue))" ViewController().urlChange(url: url)

以上是关于通过 JavaScript 事件观察 WKWebView URL 更改的问题的主要内容,如果未能解决你的问题,请参考以下文章

[笔记]《JavaScript高级程序设计》- 事件

javascript 听取可观察事件

JavaScript事件

JavaScript学习之事件

javascript面向对象--观察者模式(为对象添加事件监听功能)

Javascript 触摸对象