返回

WKWebView 手势冲突解决:优雅处理导航栏与页面回退

IOS

WKWebView手势冲突处理

WKWebView启用allowsBackForwardNavigationGestures手势,同时包含它的UINavigationController也启用了interactivePopGestureRecognizer手势时,可能出现手势冲突。两种手势在边缘滑动时都会被触发,造成页面跳转行为不确定,甚至出现崩溃。下面将分析问题原因并提供解决方案。

问题根源

冲突的核心在于WKWebViewUINavigationController对边缘滑动手势的响应机制不同。WKWebView在开启 allowsBackForwardNavigationGestures 后,会在检测到边缘滑动时,优先进行页面回退; UINavigationControllerinteractivePopGestureRecognizer 则控制导航栈的出栈行为。当两个手势同时启用,系统会根据内部的判断逻辑决定响应哪个,导致行为不一致。试图通过在 didCommitNavigation 中动态切换手势启用状态可能产生时序问题,特别是用户滑动速度很快时。此外,interactivePopGestureRecognizerWKWebView 内部视图的事件处理机制也存在竞争关系,从而导致崩溃。

解决方案

核心思路是区分手势的触发时机和操作目标。我们应根据 WKWebView 的回退历史决定当前应由哪个手势生效。

方案一:根据Webview回退状态动态启用UINavigationController手势

这个方案的思路是通过判断 webView 的回退栈是否为空,动态地禁用或者启用 navigationController 的交互式返回手势。

  1. 重写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

操作步骤:

  1. 设置当前视图控制器作为navigationController手势的delegate
  2. viewDidLoad 中执行第一步, 并根据你的实际情况创建和配置 WKWebView实例。
  3. 实现 gestureRecognizerShouldBegin,当webView.canGoBackYES时, 返回NO,禁用导航控制器手势,否则返回YES允许导航控制器手势。
  4. 实现 webView:didFinishNavigation 更新导航控制器手势和webView的手势配置。
  5. 实现updateInteractivePopGesture方法,根据webview的回退状态动态启用/禁用手势。

安全提示: 此方案较稳妥,减少了手势冲突的可能性。避免了在navigation事件发生时频繁切换手势状态导致的时序问题。

方案二:直接禁用UINavigationController交互式手势

另一个简单的方案,是直接禁用UINavigationControllerinteractivePopGestureRecognizer,而完全依赖 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来跳转上一个控制器
     }
   
 }

操作步骤:

  1. viewWillAppear生命周期函数中直接禁用 interactivePopGestureRecognizer
  2. 创建一个自定义返回按钮或手势,在回调中调用 webView goBack 进行页面回退,或者调用[self.navigationController popViewControllerAnimated:YES]退出到上一个页面。

安全提示: 这个方案更为简单,避免了复杂的冲突处理,适合页面栈较少,不依赖手势滑动进行退出的场景。但需要手动实现页面出栈的逻辑。

总结

以上提供了两种处理WKWebViewUINavigationController手势冲突的方案。应根据实际项目的需求和复杂程度,选择最合适的方案。建议优先采用第一种方案,通过 gestureRecognizerShouldBegin 方法动态控制手势是否可用, 更加安全和稳健,也能提供较好的用户体验。第二种方案则更加简单,在一些简单的页面导航场景也适用。请根据自己的实际场景选择合适的方案。