前言
在写代码过程中,遇到一个问题,使用抽屉控制器实现抽屉效果时,如果主控制器含有 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)中,按钮点击展示抽屉控制器。
设置左右导航按钮。
设置点击事件:展示抽屉效果。
///设置左右导航按钮
- (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.也可以通过点击左右导航栏中按钮对抽屉控制器进行展示。
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 中有拖拽手势,抽屉控制器也有拖拽手势两个手势冲突,导致抽屉效果失效。
解决办法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左右边缘进行滑动时,判断是否应该执行抽屉效果。
这里把两个方法的思路梳理了一下。流程图如下: