WKWebView的decidePolicyForNavigationAction加载新页面时未触发
问题现象:WKWebView初始加载内容时,
decidePolicyForNavigationAction方法正常工作,但后续加载新页面时该方法完全不触发。原本需要通过这个方法检测WebView是否要加载Web登录页,若检测到则登出用户并显示App原生登录页,但初始加载主屏幕后该方法再也没触发过。
相关实现代码:
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler { NSLog(@"decidePolicyForNavigationActionnavigationAction.request.URL.absoluteString:%@|", navigationAction.request.URL.absoluteString); if (kTestLocalHTML == 1) { decisionHandler(WKNavigationActionPolicyAllow); return; } NSURL *url = navigationAction.request.URL; if (url != nil) { // // TODO: Trim the url string below as well? // No, because this is being trimmed already in the AppManager methods related to web view URL // NSString *urlString = url.absoluteString; WKNavigationType navType = navigationAction.navigationType; // // Display the club mobile native login page and log the user out // if the webview is about to load the blazor login page URL // if ([urlString isEqualToString:[AppManager getMobileLoginUrl]]) { self.appManager.userLoginDetails = nil; [self displayLoginPage]; decisionHandler(WKNavigationActionPolicyCancel); return; } // // Check if link is tapped from webview and if URL string contains the // prepended identifier within the JavaScript injection which can be found in // "webViewDidFinishLoad" delegate method of webview // if (navType == WKNavigationTypeLinkActivated && [AppManager isWebViewURL_containsTargetBlank:urlString]) { // // Make sure to ignore the prepended identifier on the URL string // before opening it on an external browser // NSInteger targetBlankIdentifierLength = kIdentifier_TargetBlank.length; NSURL *url = [NSURL URLWithString:[urlString substringFromIndex:targetBlankIdentifierLength]]; [[UIApplication sharedApplication] openURL:url]; decisionHandler(WKNavigationActionPolicyCancel); return; } // // Check if url has custom scheme other than "http" & "https" (If is either one of the tags for Telephone "tel", Mail "mailto", SMS "sms", Web Calendar "webcal", etc.) // // Solution Reference: https://stackoverflow.com/questions/47040471/wkwebview-not-opening-custom-url-scheme-js-opens-custom-scheme-link-in-new-wind // - (Answers of both "hstdt" & "mattblessed") // // NOTE: It seems loading telephone and mail links do not work when testing on an iOS simulator. // NSString *urlScheme = url.scheme; BOOL isCustomURLscheme = ( ! [urlScheme isEqualToString:@"http"] && ! [urlScheme isEqualToString:@"https"]); if (isCustomURLscheme) { NSURL *url = [NSURL URLWithString:urlString]; [[UIApplication sharedApplication] openURL:url]; decisionHandler(WKNavigationActionPolicyCancel); return; } // // Get reference to the current URL being requested so that if there // will be failure in loading the webview, we have a reference to that URL. // self.currentUrlRequest = navigationAction.request; // Check if web view URL contains a calendar file to download if ([AppManager isWebViewURL_containsCalendarDownload:urlString]) { // // Download and process calendar file either from Events or Dining module // and display "New Event" page where user can view its details, edit and save to device calendar // [self processCalendarFileAndDisplayEventPageWithURLrequest:self.currentUrlRequest]; decisionHandler(WKNavigationActionPolicyCancel); return; } // Check if web view URL contains a PDF file attachment to download else if ([AppManager isWebViewURL_containsPDFAttachmentDownload:urlString]) { // Display document viewer page and pass few parameters for page customization (initial URL to load, navigation bar title) UIStoryboard *storyboard = [UIStoryboard storyboardWithName:kStoryboardName bundle:nil]; UINavigationController *navigationController = [storyboard instantiateViewControllerWithIdentifier:kStoryboardID_OverlayBrowserNavigationController]; OverlayBrowserViewController *vcOverlay = (OverlayBrowserViewController *)[navigationController topViewController]; vcOverlay.initialURLtoLoad = urlString; vcOverlay.navigationBarTitle = [self getPDFDocumentNameFromURLString:urlString]; [self presentViewController:navigationController animated:YES completion:nil]; decisionHandler(WKNavigationActionPolicyCancel); return; } } decisionHandler(WKNavigationActionPolicyAllow); }
可能的原因及解决方法
1. 代理对象未被正确持有
如果持有WKWebView的控制器被提前释放,或者WKNavigationDelegate的代理对象被销毁,后续的导航回调就会失效。
- 检查WKWebView实例是否被当前控制器强引用,确保
navigationDelegate设置正确:self.webView = [[WKWebView alloc] initWithFrame:self.view.bounds]; self.webView.navigationDelegate = self; [self.view addSubview:self.webView];
2. 页面使用SPA前端路由(无页面刷新)
如果加载的网页是单页应用(比如Blazor/Vue/React),页面跳转通过前端路由实现,这种情况不会触发decidePolicyForNavigationAction,因为没有发起新的HTTP请求,仅更新DOM。
- 解决方法:注入JS监听路由变化,通过
WKScriptMessageHandler通知原生代码:- 配置WKUserContentController:
WKUserContentController *userContentController = [[WKUserContentController alloc] init]; [userContentController addScriptMessageHandler:self name:@"routeChange"]; WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init]; config.userContentController = userContentController; self.webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:config]; self.webView.navigationDelegate = self; - 在页面加载完成后注入监听JS:
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { NSString *js = @"// 监听hash路由变化 window.addEventListener('hashchange', function() { window.webkit.messageHandlers.routeChange.postMessage(window.location.href); }); // 监听history模式路由变化 (function(history){ var pushState = history.pushState; history.pushState = function(state) { window.webkit.messageHandlers.routeChange.postMessage(window.location.href); return pushState.apply(history, arguments); }; var replaceState = history.replaceState; history.replaceState = function(state) { window.webkit.messageHandlers.routeChange.postMessage(window.location.href); return replaceState.apply(history, arguments); }; })(window.history);"; [webView evaluateJavaScript:js completionHandler:nil]; } - 实现
WKScriptMessageHandler接收路由变化并处理:- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message { if ([message.name isEqualToString:@"routeChange"]) { NSString *currentUrl = message.body; if ([currentUrl containsString:[AppManager getMobileLoginUrl]]) { self.appManager.userLoginDetails = nil; [self displayLoginPage]; } } }
- 配置WKUserContentController:
3. 未处理新开窗口的导航请求
如果网页中有target="_blank"的链接,WebView会尝试新开窗口,若未实现WKUIDelegate的相关方法,新开窗口的导航不会触发navigationDelegate回调。
- 实现
WKUIDelegate方法并设置代理:self.webView.UIDelegate = self; - (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures { // 在当前WebView中加载新开窗口的请求 if (!navigationAction.targetFrame.isMainFrame) { [webView loadRequest:navigationAction.request]; } return nil; // 不创建新窗口 }
4. URL匹配逻辑过于严格
代码中使用isEqualToString:进行精确匹配,如果Web登录页URL带有参数(比如token、查询参数),会导致匹配失败,误以为方法未触发。
- 先检查
NSLog是否有输出,确认方法是否真的未触发;若有输出,修改匹配逻辑为包含匹配:if ([urlString containsString:[AppManager getMobileLoginUrl]]) { self.appManager.userLoginDetails = nil; [self displayLoginPage]; decisionHandler(WKNavigationActionPolicyCancel); return; }
内容的提问来源于stack exchange,提问作者Daniel98
相关产品推荐
相关产品推荐

