[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左右邊緣進行滑動時,判斷是否應該執行抽屜效果。
這裏把兩個方法的思路梳理了一下。流程圖如下:
流程圖

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