iOS WebView提取PWA的URL参数问题(集成OneSignal需求)
解决WebView内PWA单页导航时URL不更新的问题
问题原因
你的PWA属于单页应用(SPA),前端路由通过JavaScript修改history API(pushState/replaceState)或hash实现,这类路由变化不会触发WKWebView的传统导航回调(比如decidePolicyFor)——因为没有发起新的网络请求,所以原生端无法直接捕获URL的动态变化。
解决方案
通过注入JavaScript监听前端路由变化,再通过WKScriptMessageHandler将当前URL传递给原生代码,从而实时获取URL参数。
步骤1:注入路由监听脚本
修改createWebView函数,在userContentController中添加监听路由变化的WKUserScript:
func createWebView(container: UIView, WKSMH: WKScriptMessageHandler, WKND: WKNavigationDelegate, NSO: NSObject, VC: ViewController) -> WKWebView{ let config = WKWebViewConfiguration() let userContentController = WKUserContentController() userContentController.add(WKSMH, name: "print") userContentController.add(WKSMH, name: "push-subscribe") userContentController.add(WKSMH, name: "push-permission-request") userContentController.add(WKSMH, name: "push-permission-state") // 添加用于接收路由变化的message handler userContentController.add(WKSMH, name: "routeChange") // 注入监听路由变化的JS脚本 let routeScriptSource = """ // 监听hash变化 window.addEventListener('hashchange', function() { window.webkit.messageHandlers.routeChange.postMessage(window.location.href); }); // 监听浏览器前进/后退触发的popstate事件 window.addEventListener('popstate', function() { window.webkit.messageHandlers.routeChange.postMessage(window.location.href); }); // 拦截pushState和replaceState方法(前端路由常用) const originalPushState = history.pushState; const originalReplaceState = history.replaceState; history.pushState = function() { originalPushState.apply(this, arguments); window.webkit.messageHandlers.routeChange.postMessage(window.location.href); }; history.replaceState = function() { originalReplaceState.apply(this, arguments); window.webkit.messageHandlers.routeChange.postMessage(window.location.href); }; // 页面初始加载时发送一次URL window.addEventListener('load', function() { window.webkit.messageHandlers.routeChange.postMessage(window.location.href); }); """ let routeScript = WKUserScript(source: routeScriptSource, injectionTime: .atDocumentStart, forMainFrameOnly: true) userContentController.addUserScript(routeScript) config.userContentController = userContentController // 原函数剩余代码保持不变 if #available(iOS 14, *) { config.limitsNavigationsToAppBoundDomains = true; } config.preferences.javaScriptCanOpenWindowsAutomatically = true config.allowsInlineMediaPlayback = true config.preferences.setValue(true, forKey: "standalone") let webView = WKWebView(frame: calcWebviewFrame(webviewView: container, toolbarView: nil), configuration: config) setCustomCookie(webView: webView) webView.autoresizingMask = [.flexibleWidth, .flexibleHeight] webView.isHidden = true; webView.navigationDelegate = WKND; webView.scrollView.bounces = false; webView.allowsBackForwardNavigationGestures = true webView.configuration.applicationNameForUserAgent = "Safari/604.1" webView.customUserAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1"; webView.scrollView.contentInsetAdjustmentBehavior = .never webView.addObserver(NSO, forKeyPath: #keyPath(WKWebView.estimatedProgress), options: NSKeyValueObservingOptions.new, context: nil) return webView }
步骤2:实现ScriptMessageHandler接收URL
确保你的ViewController实现WKScriptMessageHandler协议,处理routeChange消息以获取实时URL:
extension ViewController: WKScriptMessageHandler { func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { switch message.name { case "print": if let printMessage = message.body as? String { print(printMessage) } case "push-subscribe": // 保留原OneSignal相关处理逻辑 case "push-permission-request": // 保留原处理逻辑 case "push-permission-state": // 保留原处理逻辑 case "routeChange": // 接收并处理当前URL if let currentUrlString = message.body as? String, let currentUrl = URL(string: currentUrlString) { print("当前URL: \(currentUrl)") // 提取URL参数(示例:获取user_id参数) let queryItems = URLComponents(url: currentUrl, resolvingAgainstBaseURL: false)?.queryItems if let userId = queryItems?.first(where: { $0.name == "user_id" })?.value { // 此处可对接OneSignal逻辑,比如设置用户ID print("提取到用户ID: \(userId)") } } default: break } } }
补充说明
- 该方案覆盖了SPA常见的路由变化场景:
hashchange、popstate、pushState、replaceState,以及页面初始加载。 - 若只需处理特定路由,可调整JS脚本中的监听逻辑。
- 确保前端路由完成更新后再传递URL,避免获取到旧值。
内容的提问来源于stack exchange,提问作者snowsnow
相关产品推荐
相关产品推荐

