手势的使用及场景模拟

2020-06-20  本文已影响0人  哦小小树

0x01 手势冲突

手势的冲突有两种情况:

  1. 是指同一个视图,添加了多个手势
  2. 在视图层级上添加了相同的手势: 子视图有拖动手势,父视图也有拖动手势
1. 层级如下:
view  [pan, tap, pinch]

2. 层级如下:
view
    - view1[pan gesture]
        - view2[pan gesture]
            - view3[pan gesture]

以下情况不构成手势冲突:

view有两个子视图v1,v2,他们分别有一个手势,尽管v1,v2可能重合,但是他们的手势不会冲突

层级如下:不会产生手势冲突
view
    - view1[pan gesture]
    - view2[pan gesture]

总结:手势冲突的前提,手势在同一条响应链中


0x02 手势知识点考察

简单手势不用多说,设置手势和对应Target-Action事件即可。
复杂手势需要按照场景来执行不同处理。

场景一

一个scrollView可以左右切动,但是要求向右滑动到边缘位置时要求,执行push一个新控制器的功能
此时我们需要在view上添加一个pan手势,同时view上添加ScrollView,同时添加scrollView应该有的多层view

层级效果为
view [pan gesture]
  - scrollView[自带pan gesture]
      - view1
      - view2
      - view3

由于ScrollView上也有pan手势,与viewpan手势属于同一响应链上。因此会产生冲突,在边缘时右滑,会被scrollView覆盖掉view上的pan手势。

分析如下:
scrollView的手势,正常操作,但是当在边缘时,我们应该让gesture生效,此时应该不让scrollView的手势生效

解决方案一
// UIScrollView+gesture.m

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
    UIView *view = gestureRecognizer.view;
    if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
        UIPanGestureRecognizer *pan = (UIPanGestureRecognizer *)gestureRecognizer;
        CGPoint point = [pan translationInView:self];
        if (self.contentOffset.x <=0 && point.x > 0) {  // 说明是继续向右滑动,此时需要将事件交由其他视图响应
            return NO;
        }
    }
    return [super gestureRecognizerShouldBegin:gestureRecognizer];
}
解决方案二

如果不想给scrollView编写分类或者编写子类。可以直接在View的手势中做处理.

// ViewController.m; 记得需要设置view.gesture.delegate = self;
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
    return YES;
}
// ViewController.m
- (BOOL)gestureRecognizerShouldBegin:(UIPanGestureRecognizer *)gestureRecognizer {
    if (![gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
        return YES;
    }
    
    if ([gestureRecognizer translationInView:self.view].x > 0 && self.mainView.contentOffset.x == 0) { // 代表是向右滑动,切scrollView滚动到左边界
        return YES;
    }
    return NO;
}

场景二

在视图上添加一个button,由于button的层级在底层,导致上层的gesture覆盖了button
我们需要在接收事件时做下判断,判断是否点击区域在button

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
    // 判读当前view是否在button的位置,如果在就返回NO,此时就可以满足我么的要求达到效果
    CGPoint point = [touch locationInView:self.view];
    return !CGRectContainsPoint(self.button.frame, point);
}

场景三

如果scrollView上有slider,并且scrollView底部还有view是有pan手势的。

// 层级效果为
view[pan gesture]
    - scrollView
        -view
              - slider
        -view
        -view

目标:Slider正常操作,ScrollView正常操作,view手势再边缘正常操作

解决方案一
// ViewController.m
- (BOOL)gestureRecognizerShouldBegin:(UIPanGestureRecognizer *)gestureRecognizer {
    if (![gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
        return YES;
    }
    
    if ([gestureRecognizer translationInView:self.view].x > 0 && self.mainView.contentOffset.x == 0) { // 代表是向右滑动,切scrollView滚动到左边界
        return YES;
    }
    return NO;
}

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
    return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
    CGPoint location = [touch locationInView:self.view];

    return !CGRectContainsPoint(self.slider.frame, location);
}

如果我们使用场景一的方案一来做第一步处理,第二步也是一样


0x03 手势代理方法含义及执行顺序【从上向下执行】

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveEvent:(UIEvent *)event API_AVAILABLE(ios(13.4), tvos(13.4)) API_UNAVAILABLE(watchos);
/* 在touchesBegan之前调用,在手势识别器上被调用以进行新的触摸,返回NO,阻止手势识别器看到此触摸, 使用此方法,一般是用来方式手势覆盖Button等的点击事件 */
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch ;
/* 在pressBegan:withEvent:之前调用,在手势识别器上进行新的按压操作,返回NO阻止手势看到这个按压事件 */
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceivePress:(UIPress *)press ;

以下两个方法,优先调用OfGesture

  1. 如果没实现或者返回NO,才会去调用ToFailByGesture;
  2. 如果返回YES,那么就不会再调用ToFail,因为ToFail等级低,返回YES,代表gesture优先级高,返回NO又是默认的情况,保持原有特性
/*
 * 默认返回为NO,代表gestureRecognizer在冲突时优先处理。
 * 返回YES则otherGesture优先处理,等失败了才会处理gesture
 * 翻译为中文: gestu 是否应该依赖于OtherGesture的失败, 翻译为是否gesture应该在otherGesture失败后才去执行
 */
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer API_AVAILABLE(ios(7.0));



/* 默认返回NO。返回YES,代表只有当gestureRecognizer返回Failes时,otherGesturer才会被识别, 相当于延迟等待功能
 *  注意,返回NO的话,等同于无效,只有在需要指定情形时返回YES,才会按照我们设想进行,如果想让otherGesture优先级高,不如使用上面方法
 */
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer API_AVAILABLE(ios(7.0));

/* 当手势识别器尝试从UIGestureRecognizerStatePossible转换时调用。返回NO将导致它转换为UIGestureRecognizerStateFailed
 * YES(默认设置)指示手势识别器继续解释触摸,否则阻止其尝试识别其手势。 */

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer;

/* 只有当响应链上有出现冲突手势时才会去调用这个方法,判断是否需要阻塞掉,或者同时执行
 * 判断两个手势,是否会有一个手势识别器被另一个阻塞掉
 * 返回YES,表示允许两个手势都被识别,默认实现返回NO,表示两个手势同时只能实现一个
 * 注意: 返回YES,是保证允许两个手势被同时触发。返回NO并不能确保两个手势一定不会同时触发,因为另一个手势的代理可能返回YES
 * 流程是:上层先接触到手势,如果返回NO,表示阻止继续向下传递手势,如果返回YES,则手势会继续向下层传递, 再持续走同样的流程
 * 注意:如果一个手势使用返回了NO,但是另一个返回了YES,系统会使用YES,意思即是,响应链中有一个实现YES,那么就会生效
 */
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer;


0x04 总结

手势处理,一定要注意手势的层级结构:
一般来说层级为响应链的层级结构,从响应的手势,如果处理不了会顺着响应链向UIWindow查找,等到找合适的手势再做处理,中间可能会出现手势识别同时,延迟操作等事件。

上一篇下一篇

猜你喜欢

热点阅读