通过 JavaScript 事件观察 WKWebView URL 变化的问题
Issue with observing WKWebView URL changes via JavaScript events
我有一个显示 Laravel 网络应用程序的 WKWebView。我目前有我的 ViewController 设置来捕捉 URL 变化——它捕捉了大部分变化。但是,WKWebView 似乎忽略了通过 JavaScript 事件进行的少量 URL 更改。
我已经尝试了以下所有解决方案并在下面的代码中实现了它们:
- How to detect hash changes in WKWebView?
- using KVO to observe WKWebView's URL property not work in iOS 10
在我展示代码之前有一个有趣的注意事项:它能够检测到简单的#hash 更改,但无法检测到从第 1 页加载到第 2 页的 JavaScript 事件操作(这是 URL 我试图抓住的变化):
https://myweb.com/dashboard
→https://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"
.
意思是,这两行观察者代码是唯一捕捉到这个变化的东西,但没有return那个变化的值:
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 搜索栏中;因此,我不认为这是一个与基于网络的代码有关的问题(即框架,URL 欺骗)。
但是,我无法确认这一点,因为它是一个基于网络的 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/dashboard
和 https://myweb.com/dashboard/project/new
)。
此解决方案使用较旧的 KVO 方法来实现与@Rudedog 相同的结果,但有更多的负担(即,您仍然需要删除观察者并处理独立的键路径)。请参阅下面@Rudedog 的回答以获得更现代的解决方案。
编辑 3(KVO 解决方案)
我能够将 KVO WKWebView.url
分配给一个字符串变量。从那里,我能够将字符串值传递给一个函数,然后处理我正在寻找的每个输出:
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
}
}
在你继续之前,我强烈建议你使用现代 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.
}
}
这样做可以让您的代码在调试问题时更清晰、更容易理解。
我有一个显示 Laravel 网络应用程序的 WKWebView。我目前有我的 ViewController 设置来捕捉 URL 变化——它捕捉了大部分变化。但是,WKWebView 似乎忽略了通过 JavaScript 事件进行的少量 URL 更改。
我已经尝试了以下所有解决方案并在下面的代码中实现了它们:
- How to detect hash changes in WKWebView?
- using KVO to observe WKWebView's URL property not work in iOS 10
在我展示代码之前有一个有趣的注意事项:它能够检测到简单的#hash 更改,但无法检测到从第 1 页加载到第 2 页的 JavaScript 事件操作(这是 URL 我试图抓住的变化):
https://myweb.com/dashboard
→https://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"
.
意思是,这两行观察者代码是唯一捕捉到这个变化的东西,但没有return那个变化的值:
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 搜索栏中;因此,我不认为这是一个与基于网络的代码有关的问题(即框架,URL 欺骗)。
但是,我无法确认这一点,因为它是一个基于网络的 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/dashboard
和 https://myweb.com/dashboard/project/new
)。
此解决方案使用较旧的 KVO 方法来实现与@Rudedog 相同的结果,但有更多的负担(即,您仍然需要删除观察者并处理独立的键路径)。请参阅下面@Rudedog 的回答以获得更现代的解决方案。
编辑 3(KVO 解决方案)
我能够将 KVO WKWebView.url
分配给一个字符串变量。从那里,我能够将字符串值传递给一个函数,然后处理我正在寻找的每个输出:
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
}
}
在你继续之前,我强烈建议你使用现代 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.
}
}
这样做可以让您的代码在调试问题时更清晰、更容易理解。