WKWebView 手势冲突解决:优雅处理导航栏与页面回退
2025-02-01 18:01:23
WKWebView手势冲突处理
当WKWebView
启用allowsBackForwardNavigationGestures
手势,同时包含它的UINavigationController
也启用了interactivePopGestureRecognizer
手势时,可能出现手势冲突。两种手势在边缘滑动时都会被触发,造成页面跳转行为不确定,甚至出现崩溃。下面将分析问题原因并提供解决方案。
问题根源
冲突的核心在于WKWebView
和UINavigationController
对边缘滑动手势的响应机制不同。WKWebView
在开启 allowsBackForwardNavigationGestures
后,会在检测到边缘滑动时,优先进行页面回退; UINavigationController
的 interactivePopGestureRecognizer
则控制导航栈的出栈行为。当两个手势同时启用,系统会根据内部的判断逻辑决定响应哪个,导致行为不一致。试图通过在 didCommitNavigation
中动态切换手势启用状态可能产生时序问题,特别是用户滑动速度很快时。此外,interactivePopGestureRecognizer
与 WKWebView
内部视图的事件处理机制也存在竞争关系,从而导致崩溃。
解决方案
核心思路是区分手势的触发时机和操作目标。我们应根据 WKWebView
的回退历史决定当前应由哪个手势生效。
方案一:根据Webview回退状态动态启用UINavigationController手势
这个方案的思路是通过判断 webView 的回退栈是否为空,动态地禁用或者启用 navigationController 的交互式返回手势。
-
重写
UIGestureRecognizerDelegate
方法:我们需要实现
UIGestureRecognizerDelegate
协议中的gestureRecognizerShouldBegin
方法来动态控制interactivePopGestureRecognizer
是否可用。当WKWebView
可以后退时,应该禁用interactivePopGestureRecognizer
;当webView无法后退时才启用。
#import "ViewController.h"
@interface ViewController () <UIGestureRecognizerDelegate>
@end
@implementation ViewController
-(void)viewDidLoad{
[super viewDidLoad];
self.navigationController.interactivePopGestureRecognizer.delegate = self;
//其他初始化操作,包括初始化 WKWebView 实例并添加到视图
}
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
if (gestureRecognizer == self.navigationController.interactivePopGestureRecognizer){
if(self.webView.canGoBack){
return NO; //禁用 UINavigationController手势
}else{
return YES;//启用 UINavigationController手势
}
}
return YES;
}
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
[self updateInteractivePopGesture];
}
-(void)updateInteractivePopGesture{
if (self.webView.canGoBack) {
self.navigationController.interactivePopGestureRecognizer.enabled = NO; //不使用UINavigationController的手势返回
self.webView.allowsBackForwardNavigationGestures = YES; //使用webview手势返回
}else{
self.navigationController.interactivePopGestureRecognizer.enabled = YES; //启用 UINavigationController 手势
self.webView.allowsBackForwardNavigationGestures = NO; //禁用 webView手势
}
}
@end
操作步骤:
- 设置当前视图控制器作为
navigationController
手势的delegate
。 - 在
viewDidLoad
中执行第一步, 并根据你的实际情况创建和配置WKWebView
实例。 - 实现
gestureRecognizerShouldBegin
,当webView.canGoBack
为YES
时, 返回NO
,禁用导航控制器手势,否则返回YES
允许导航控制器手势。 - 实现
webView:didFinishNavigation
更新导航控制器手势和webView的手势配置。 - 实现
updateInteractivePopGesture
方法,根据webview的回退状态动态启用/禁用手势。
安全提示: 此方案较稳妥,减少了手势冲突的可能性。避免了在navigation事件发生时频繁切换手势状态导致的时序问题。
方案二:直接禁用UINavigationController交互式手势
另一个简单的方案,是直接禁用UINavigationController
的interactivePopGestureRecognizer
,而完全依赖 WKWebView
的手势进行页面回退。当需要退出到上一层导航控制器时,在逻辑层面使用 [self.navigationController popViewControllerAnimated:YES]
来实现。
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
self.navigationController.interactivePopGestureRecognizer.enabled = NO; //禁用UINavigationController交互式手势
self.webView.allowsBackForwardNavigationGestures = YES;// 启用webiview的手势返回
}
-(void)backButtonClicked:(UIButton*)sender{
if([self.webView canGoBack]){
[self.webView goBack];
}else{
[self.navigationController popViewControllerAnimated:YES];//调用 popViewController来跳转上一个控制器
}
}
操作步骤:
- 在
viewWillAppear
生命周期函数中直接禁用interactivePopGestureRecognizer
。 - 创建一个自定义返回按钮或手势,在回调中调用
webView goBack
进行页面回退,或者调用[self.navigationController popViewControllerAnimated:YES]
退出到上一个页面。
安全提示: 这个方案更为简单,避免了复杂的冲突处理,适合页面栈较少,不依赖手势滑动进行退出的场景。但需要手动实现页面出栈的逻辑。
总结
以上提供了两种处理WKWebView
和UINavigationController
手势冲突的方案。应根据实际项目的需求和复杂程度,选择最合适的方案。建议优先采用第一种方案,通过 gestureRecognizerShouldBegin
方法动态控制手势是否可用, 更加安全和稳健,也能提供较好的用户体验。第二种方案则更加简单,在一些简单的页面导航场景也适用。请根据自己的实际场景选择合适的方案。