美文网首页
手势的使用及场景模拟

手势的使用及场景模拟

作者: 哦小小树 | 来源:发表于2020-06-20 11:36 被阅读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的手势生效

    解决方案一
    • 编写ScrollView分类,实现以下方案,当达到边缘时,让scrollView不响应,进而将响应交由上层去处理,即父视图viewpan手势
    // 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的手势中做处理.

    • 首先需要让父视图viewpan手势优先级更高,要比scrollView的手势优先级高
    // ViewController.m; 记得需要设置view.gesture.delegate = self;
    - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
        return YES;
    }
    
    • 其次需要在指定时机将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;
    }
    
    

    场景二

    在视图上添加一个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手势再边缘正常操作

    解决方案一
    • 先解决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;
    }
    
    • 解决sliderUIScrollView的冲突问题
      UIScrollView中有一个属性为delayContentTouches,意味延迟内容事件的传送,这样就有150ms的时机来作为UIScrollView的事件处理,等超出了这个事件后便会交给内容处理,这里我们直接将其设置为YES,就可以解决此问题。

    • 解决sliderviewpan手势问题
      在非滑块区域直接拖动会交事件给viewpan手势。 这是因为我们使用了场景一的第二方案会将viewpan手势优先处理。
      我们可以通过场景二的方式来禁用viewpan手势

    - (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查找,等到找合适的手势再做处理,中间可能会出现手势识别同时,延迟操作等事件。

    相关文章

      网友评论

          本文标题:手势的使用及场景模拟

          本文链接:https://www.haomeiwen.com/subject/vhswxktx.html