You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS Swift中WKWebView取消导航后无法调用evaluateJavaScript及接收事件

WKWebView取消非绑定域导航后JS通信失效的解决方法

问题背景

在iOS 15.0环境下使用Swift开发React Web应用的壳App,已通过WKUserScript和evaluateJavaScript实现原生与Web层的双向通信,且已在Info.plist中配置WKAppBoundDomains,功能正常。但在WKNavigationDelegate的webView(_:decidePolicyFor:decisionHandler:)中调用decisionHandler(.cancel)阻止非主域名或自定义Scheme(如another-app://)的导航后,出现以下问题:

  • userContentController(_:didReceive:)无法接收Web层消息
  • evaluateJavaScript抛出WKErrorDomain Code=14错误,提示目标帧不在App绑定域内

代码演示及现象:

// Executing javascript in WebView
webView.evaluateJavaScript("window.location.href;") { result, error in
    print(result, error)
}

// Cancelling navigation to the external URL
print("Cancelling navigation")
decisionHandler(.cancel) // 此操作导致WebView状态异常

// Executing javascript in WebView, **just as before**
webView.evaluateJavaScript("window.location.href;") { result, error in
    print(#line, result, error)
}

输出:

> Optional("http://my-url.com")
> Cancelling navigation
> Optional(Error Domain=WKErrorDomain Code=14 "JavaScript execution targeted a frame that is not in an app-bound domain" UserInfo={WKJavaScriptExceptionLineNumber=0, WKJavaScriptExceptionMessage=Unable to execute JavaScript in a frame that is not in an app-bound domain, WKJavaScriptExceptionColumnNumber=0, NSLocalizedDescription=JavaScript execution targeted a frame that is not in an app-bound domain})

问题原因

WKWebView在处理导航请求时,即使调用decisionHandler(.cancel)取消导航,内部仍会将当前帧的上下文切换为目标URL的域。由于目标URL不在WKAppBoundDomains列表中,后续的JS执行和消息通信会被系统限制,导致上述异常。

解决方案

方案1:从Web层拦截导航请求(推荐)

在Web层通过JS拦截外部链接和自定义Scheme的导航操作,直接向原生发送消息处理,避免触发原生的导航事件,从根源上避免WebView进入异常状态。

Web层JS代码:

document.addEventListener('click', (e) => {
    const linkElement = e.target.closest('a');
    if (!linkElement || !linkElement.href) return;
    
    const url = new URL(linkElement.href);
    const allowedDomain = 'my-url.com';
    
    // 拦截非主域名或自定义Scheme的链接
    if (url.host !== allowedDomain || url.scheme === 'another-app') {
        e.preventDefault();
        // 发送消息给原生处理
        window.webkit.messageHandlers.nativeBridge.postMessage({
            action: 'handleExternalURL',
            url: linkElement.href
        });
    }
});

原生Swift代码:

func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
    guard let body = message.body as? [String: Any],
          let action = body["action"] as? String,
          let urlStr = body["url"] as? String,
          let url = URL(string: urlStr) else {
        return
    }
    
    if action == "handleExternalURL" {
        if UIApplication.shared.canOpenURL(url) {
            UIApplication.shared.open(url)
        }
    }
}

方案2:原生层拦截后恢复绑定域上下文

如果必须在原生层处理导航拦截,在调用decisionHandler(.cancel)后,立即重新加载原绑定域页面,将WebView上下文恢复到允许JS交互的状态。

Swift代码:

func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
    guard let url = navigationAction.request.url else {
        decisionHandler(.allow)
        return
    }
    
    let mainDomain = "my-url.com"
    // 处理自定义Scheme
    if url.scheme == "another-app" {
        if UIApplication.shared.canOpenURL(url) {
            UIApplication.shared.open(url)
        }
        decisionHandler(.cancel)
        // 恢复原页面上下文
        webView.load(URLRequest(url: URL(string: "http://\(mainDomain)")!))
        return
    }
    
    // 处理非主域名的HTTP/HTTPS链接
    if url.host != mainDomain {
        decisionHandler(.cancel)
        // 恢复原页面上下文
        webView.load(URLRequest(url: URL(string: "http://\(mainDomain)")!))
        return
    }
    
    decisionHandler(.allow)
}

方案3:针对自定义Scheme的优化处理

对于自定义Scheme,可直接调用系统打开外部App,无需等待WebView加载请求,确保cancel操作不会影响当前页面上下文:

func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
    guard let url = navigationAction.request.url else {
        decisionHandler(.allow)
        return
    }
    
    if url.scheme == "another-app" {
        // 优先处理外部App打开
        if UIApplication.shared.canOpenURL(url) {
            UIApplication.shared.open(url)
        }
        // 立即cancel,避免WebView加载该Scheme
        decisionHandler(.cancel)
        return
    }
    
    // 其他导航逻辑
    decisionHandler(.allow)
}

内容的提问来源于stack exchange,提问作者Rasmus Englund

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 01:35:26