[iOS] 解决MMDrawController与scrollView手势共存问题

前言

在写代码过程中,遇到一个问题,使用抽屉控制器实现抽屉效果时,如果主控制器含有 scrollView,那么 scrollView 的滑动会与 MMDrawController 的滑动产生冲突,从而无法实现抽屉效果,经过尝试找到两种解决办法,写了一个简单 Demo
这里就简单的介绍一下 MMDrawController 的使用及如何解决 MMDrawController 与 scrollView 手势共存问题。


MMDrawController的简单介绍

MMDrawController 是一个非常实用的抽屉控制器第三方库,详情使用请参考 GitHub-MMDrawController

Demo 中减掉一些不常用的功能,实现抽屉效果使用MMDrawController分为两步:

1.设置抽屉控制器。

2.展示抽屉控制器。

第一步设置抽屉控制器

在 AppDelegate 的 didFinishLaunchingWithOptions 方法中,设置显示抽屉控制器。

​ 1.初始化控制器(左抽屉控制器、右抽屉控制器、中间导航主控制器)。

​ 2.使用 MMDrawController 实现左右抽屉效果。

​ 3.设置打开/关闭抽屉的手势。

​ 4.设置左右抽屉控制器的显示宽度。

​ 5.显示抽屉控制器。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    //1.初始化控制器
    UIViewController *centerVC = [[drawerDemoCenterViewController alloc]init];
    UIViewController *leftVC = [[drawerDemoLeftViewController alloc]init];
    UIViewController *rightVC = [[drawerDemoLRightViewController alloc]init];
    UINavigationController *centerNavVC = [[UINavigationController alloc]initWithRootViewController:centerVC];

    //2.使用MMDrawerController
    self.drawerController = [[MMDrawerController alloc]initWithCenterViewController:centerNavVC leftDrawerViewController:leftVC rightDrawerViewController:rightVC];

    //3.设置打开/关闭抽屉的手势
    self.drawerController.openDrawerGestureModeMask = MMOpenDrawerGestureModeAll;
    self.drawerController.closeDrawerGestureModeMask =MMCloseDrawerGestureModeAll;

    //4.设置左右两边抽屉显示的宽度
    self.drawerController.maximumLeftDrawerWidth = 200.0;
    self.drawerController.maximumRightDrawerWidth = 300.0;

    //5、初始化窗口、设置根控制器、显示窗口
    self.window = [[UIWindow alloc]initWithFrame:[UIScreen mainScreen].bounds];
    [self.window setRootViewController:self.drawerController];
    [self.window makeKeyAndVisible];

    return YES;

第二步展示抽屉控制器

在主控制器(centerNavVC.m)中,按钮点击展示抽屉控制器。

  1. 设置左右导航按钮。

  2. 设置点击事件:展示抽屉效果。

///设置左右导航按钮
- (void)setupNavgation {
    self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"左抽屉" style:UIBarButtonItemStylePlain target:self action:"didTapLeftMenuButton"];
    self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"右抽屉" style:UIBarButtonItemStylePlain target:self action:"didTapLeftMenuButton"];
}

///设置点击事件:展示抽屉效果
- (void)didTapLeftBarButton {
    //展示左抽屉控制器
    [self.mm_drawerController toggleDrawerSide:MMDrawerSideLeft animated:YES completion:nil];
}
- (void)didTapRightBarButton {
    //展示右抽屉控制器
    [self.mm_drawerController toggleDrawerSide:MMDrawerSideRight animated:YES completion:nil];
}

Gif示例:

一个简单的抽屉控制器就完成了,如图所示:

1.可以通过左右滑动,在主控制器左右展示抽屉控制器。

2.也可以通过点击左右导航栏中按钮对抽屉控制器进行展示。

ViewDemo

MMDrawControllerscrollView 手势共存问题

问题描述

实现抽屉效果时,如果主控制器含有 scrollView,那么 scrollView 的滑动会与 MMDrawController 的滑动产生冲突,,从而无法实现抽屉效果。

设置 ScrollView

在主控制器中添加 scrollView 的步骤大概为以下三步:

1.创建 ScrollView 及三个子 View:设置尺寸、颜色:

  UIView *view1 = [[UIView alloc]initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH,SCREEN_HEIGHT)];
  UIView *view2 = [[UIView alloc]initWithFrame:CGRectMake(1 * SCREEN_WIDTH, 0, SCREEN_WIDTH,SCREEN_HEIGHT)];
  UIView *view3 = [[UIView alloc]initWithFrame:CGRectMake(2 * SCREEN_WIDTH, 0, SCREEN_WIDTH,SCREEN_HEIGHT)];
  self.contentScrollView.frame = self.view.frame;
  view1.backgroundColor = [UIColor redColor];
  view2.backgroundColor = [UIColor yellowColor];
  view3.backgroundColor = [UIColor blueColor];

2.添加 ScrollView 及三个子 View:

    [self.view addSubview:self.contentScrollView];
    [self.contentScrollView addSubview:view1];
    [self.contentScrollView addSubview:view2];
    [self.contentScrollView addSubview:view3];

3.设置 scrollView 属性,关闭弹簧效果 、 启动分页效果 、 滚动范围为3倍屏幕宽度:

    self.contentScrollView.bounces = NO;
    self.contentScrollView.pagingEnabled = YES;
    self.contentScrollView.contentSize = CGSizeMake(self.view.frame.size.width * 3,0);
}

Gif示例:

启动模拟器后,显示如图所示:

1.scrollView 可以正常滑动。

2.点击抽屉按钮,抽屉效果显示正常。

3.左右滑动的时候抽屉效果失效。

scrollView与抽屉冲突

分析原因:

scrollView 中有拖拽手势,抽屉控制器也有拖拽手势两个手势冲突,导致抽屉效果失效。

解决办法1:

方法1:使用 scrollView 代理方法,此方法中有一个参数: targetContentOffset ,可以通过 targetContentOffset 变量来获取滑动偏移量,若偏移量X方向

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset { }

该方法使用条件是:
- 控制器成为 ScrollView 的代理,准守代理协议

该方法使用场景:
- 滑动 scrollView,并且手指离开时执行。一次有效滑动,只执行一次。

该方法功能介绍:
- 该方法中有一个非常重要的参数 targetContentOffset 滑动位移指针。

  • 通过该指针 targetContentOffset->x 获取,手指离开时 scrollView 在 X 方向滑动偏移量: tagetX

  • 使用 scrollView 在 X 方向滑动偏移量 tagetX,进行判断:

    1.1 若:滑动后tagetX为最小(最小的 X 值:0) 并且 显示在 ScrollView 最左侧(contentOffsetX 为最小值:0)

    • 展开左抽屉效果。

    1.2 若:滑动后 tagetX 为最大(最大的 X 值:2倍屏幕宽度) 并且 显示在 ScrollView 最左侧(contentOffsetX 为最小值:0)

    • 展开右抽屉效果。

    1.3 若:不满足以上两项,则不进行抽屉效果展示。

代码示例

//成为代理,准守协议,执行方法
self.contentScrollView.delegate = self; 

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
//获取滑动偏移量
    float tagetX = targetContentOffset->x;
//向左滑动时: 如果滑动后的X为最小(最小的X值),并且 为第一个控制器(contentOffsetX 为最小0)
//向右滑动时: 如果滑动后的X为最大(最大的X值),并且为最后一个控制器(contentOffsetX 为最大)
    if (tagetX == 0 && self.contentScrollView.contentOffset.x == 0 * SCREEN_WIDTH) {
        [self.mm_drawerController toggleDrawerSide:MMDrawerSideLeft animated:YES completion:nil];
    } else if (tagetX == 2 * SCREEN_WIDTH  && self.contentScrollView.contentOffset.x == 2 * SCREEN_WIDTH) {
        [self.mm_drawerController toggleDrawerSide:MMDrawerSideRight animated:YES completion:nil];
    }
}

解决办法2:

方法2:给 scrollView 添加一个空手势,使用手势的代理方法。

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {}

该方法使用条件是:
- 控制器成为手势的代理,准守代理协议。

该方法使用场景:
- 当执行手势时.会调用该方法。

该方法功能介绍:

  • 当执行手势时,询问该手势接收者是否应该开始执行执行手势方法:
    • 若:return YES ,该手势会被拦截(不会传递给 ScrollView 进行滚动)。
    • 若:return NO, 该手势会传递下去(传递给 ScrollView 进行滚动)。
  • 在该方法中进行判断:

1 若:手势不为滑动手势,直接 return NO,该手势会传递下去(传递给 ScrollView 进行滚动)。

2 若:手势为滑动手势,在进行判断:

2.1 若:该滑动手势滑动距离(滑动方向向右并且此时 scrollView 的 X 在最左边),展开左抽屉效果;

  • 展开左抽屉效果。

  • return YES,该手势会被拦截(不会传递给ScrollView进行滚动)。

2.2 若:该滑动手势滑动距离(滑动方向向做并且此时 scrollView 的 X 在最右边),展开右抽屉效果;

  • 展开右抽屉效果。

  • return YES,该手势会被拦截(不会传递给ScrollView进行滚动)。

2.3 若:不满足以上两点,则return NO,该手势会传递下去(传递给ScrollView进行滚动)。

代码示例

//创建一个空手势
  UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:nil];
//添加空手势
  [self.contentScrollView addGestureRecognizer:pan]; 
//成为代理,准守协议,执行代理方法
  pan.delegate = self;

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
// 判断手势是否为 滑动手势,
  if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
    UIPanGestureRecognizer *pan = (UIPanGestureRecognizer *)gestureRecognizer;
    CGPoint pos = [pan velocityInView:pan.view];
//判断是否展开左侧或右侧抽屉控制器
    if (pos.x > SCREEN_WIDTH /3 && self.contentScrollView.contentOffset.x == 0 * SCREEN_WIDTH) {
      [self.mm_drawerController toggleDrawerSide:MMDrawerSideLeft animated:YES completion:nil];
      return YES;
    } else if (pos.x < -SCREEN_WIDTH /3 && self.contentScrollView.contentOffset.x == 2 * SCREEN_WIDTH) {
      [self.mm_drawerController toggleDrawerSide:MMDrawerSideRight animated:YES completion:nil];
      return YES;
    }
  }
  return NO;
}

Gif示例:

启动模拟器后,显示如图所示:

1.scrollView 可以正常滑动。

2.点击抽屉按钮,抽屉效果显示正常。

3.左右滑动 scrollView 抽屉控制器与scrollView 不冲突。
解决ScrollView与抽屉冲突

小结

解决该问题的核心为是在ScrollView左右边缘进行滑动时,判断是否应该执行抽屉效果。
这里把两个方法的思路梳理了一下。流程图如下:
流程图

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章