前言
在寫代碼過程中,遇到一個問題,使用抽屜控制器實現抽屜效果時,如果主控制器含有 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左右邊緣進行滑動時,判斷是否應該執行抽屜效果。
這裏把兩個方法的思路梳理了一下。流程圖如下: