zoukankan      html  css  js  c++  java
  • 一个Bug引发的对UIGestureRecognizer的思考

    最近的一个项目中使用了两个功能  
    * `抽屉`  
    * `悬浮按钮`    
    
    这个两个功能都跟用户的手势交互紧密相关 
    
    
    抽屉  
    * `滑动开关抽屉`    
    * `点击开关抽屉`  
    
    悬浮按钮  
    * `拖动按钮`  
    * `点击事件`  
    
    ---
    
    ##BUG
    这两个功能都较为普遍,所以我和同事一人在网上找了一个相关的demo来完成。
    
    不过最后这两个功能出现了`冲突`:  
    > 在拖动悬浮按钮的时候,抽屉的功能也被触发,造成两者都不能顺畅执行,两者同时滑动一下之后,按钮就停止运动,而抽屉继续完成剩余行为。  
    > 这个结果和原本预想的不一样,原本的预计是,当用户交互发生在按钮的时候,抽屉的手势不应该被触动,只有在交互发生在悬浮按钮之外的时候,抽屉的手势才会被触动。
    
    可见,在抽屉和悬浮按钮上,`触摸`有一定的`冲突`。
    
    ---
    
    ##BUG原因
    我查看了两个demo中对手势的完成方式。 
    
    抽屉中使用了`UIPanGestureRecognizer`  
    
    悬浮按钮通过重写了`Touch-Event Handling Methods`
    * touchesBegan:withEvent:
    * touchesMoved:withEvent:
    * touchesEnded:withEvent:
    * touchesCancelled:withEvent:
    
    通过设置log值,我观察了一下问题产生时候程序调用的流程。
    
    ```
    2014-02-26 11:32:03.895 Gesture[1486:60b] button touch began
    2014-02-26 11:32:03.989 Gesture[1486:60b] button touch moved
    2014-02-26 11:32:04.005 Gesture[1486:60b] view panAction
    2014-02-26 11:32:04.008 Gesture[1486:60b] button touch cancelled
    2014-02-26 11:32:04.021 Gesture[1486:60b] view panAction
    2014-02-26 11:32:04.023 Gesture[1486:60b] view panAction
    ```
    
    发现当悬浮按钮的`touch began` 和 `touch moved`方法调用后,抽屉的手势起了作用,同时原本悬浮按钮的`touch cancel`被触发。接下来只执行抽屉的手势。
    这就是为什么会发生我之前描述的问题的原因。
    
    对于这一点[Apple文档](https://developer.apple.com/library/ios/documentation/uikit/reference/UIGestureRecognizer_Class/Reference/Reference.html#//apple_ref/occ/instp/UIGestureRecognizer/cancelsTouchesInView)描述的相当清楚  
    > A gesture recognizer operates on touches hit-tested to a specific view and all of that view’s subviews.   
    > ......  
    > cancelsTouchesInView—If a gesture recognizer recognizes its gesture, it unbinds the remaining touches of that gesture from their view (so the window won’t deliver them). The window cancels the previously delivered touches with a (touchesCancelled:withEvent:) message. If a gesture recognizer doesn’t recognize its gesture, the view receives all touches in the multi-touch sequence.
    
    当手势添加到view上的时候,手势会开始观察view和view上的subviews。  
    当手势被识别的时候,之前的touch将被取消同时不会再传递  
    当然这个可以通过设置cancelsTouchesInView为NO来取消或者开启,具体的可以看看[Apple文档](https://developer.apple.com/library/ios/documentation/uikit/reference/UIGestureRecognizer_Class/Reference/Reference.html#//apple_ref/occ/instp/UIGestureRecognizer/cancelsTouchesInView)
    
    ---
    
    ##疑问
    在我原本的印象当中,UIGestureRecognizer是对Touch-Event Handling Methods的高一层封装,就算两者同时使用,本质依旧相同,按理说应该不会发生这些问题。当悬浮按钮截获了交互之后,就不应该继续传到superview上去,也就不应该触发抽屉的手势。
    但是最终结果是两者都触发了。
    
    现在一想,发现自己对UIGestureRecognizer的第一印象是错误的。  
    UIGestureRecognizer的UITouch获得和普通的Responder的UITouch传递还不太一样。
    所以我花了点时间好好看了一下苹果的文档[Event Handling Guide for iOS](https://developer.apple.com/library/ios/documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/GestureRecognizer_basics/GestureRecognizer_basics.html#//apple_ref/doc/uid/TP40009541-CH2-SW44)
    
    ```
    以下是我看文档前两个疑惑:  
    1. UITouch是如何传递的 
    2. 父视图的UIGestureRecognizer是如何早于子视图获取到UITouch的
    ```
    
    ---
    
    ## Gesture Recognizer对UITouch的影响
    
    我们和手机交互的主要方式是通过屏幕,我们的一些手势也都是在屏幕上操作,所以我们最早的触摸当然是被电容屏所接收到的。当然这个回答不是我们想要的答案。我们所关心的是在电容屏所接收到的触摸在系统里的处理。  
    那么在电容屏接收到触摸后,这些触摸首先去了哪里呢?
    
    Apple文档对于这个问题给出了一副图来说明:  
    ![图1](https://developer.apple.com/library/ios/documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/Art/path_of_touches_2x.png)
      
    1. 用户的手势交互以UITouch和UIEvent的形式存储在当前应用程序的事件队列中。
    2. UIApplication单例将对象将事件从队列的顶部取出,然后派发下去到焦点窗口即拥有当前用户事件焦点的窗口(当前应用程序窗口UIWindow)
    3. UIWindow在把touch传递给view上的手势识别器。
    4. 如果手势识别器识别成功,不再传递给view,识别失败则传递给view
    
    描述概念总是头疼的,简单来打个比方,UIWindow就是一个后妈,手势识别器就是后妈的亲儿子,view呢是不是亲儿子,后妈每次拿到好吃的都是先分给亲儿子先,如果亲儿子喜欢吃,就不会再给另外一个儿子吃了,不仅不让吃,还会把之前的给的全拿回来,只有亲儿子不吃的东西才会丢给另外一个儿子吃。
    
    这里面涉及了UIGestureRecognizer和Event Handle Method的方法调用,再来一张图配合理解。  
    ![图2](https://developer.apple.com/library/ios/documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/Art/recognize_touch_2x.png)
    
    扯了个蛋,自己也理解的通透了,挺好挺好。
      
    
    ##后记  
    这篇博客主要记录了我对于bug发生原因的记录,不过在查询文档过程中,还看到了很多其他相关的内容,不过鉴于时间有限,也没法一下子记录下来。等空下来的时候,我会自己再总结一下响应链方面的机制。最后不得不说,苹果文档真是IOS开发者的好朋友!
    
    
  • 相关阅读:
    【springcloud alibaba】配置中心之nacos
    【springcloud alibaba】注册中心之nacos
    LeetCode计数质数Swift
    LeetCode移除链表元素Swift
    LeetCode删除重复的电子邮箱SQL
    LeetCode汉明距离Swift
    LeetCode两整数之和Swift
    LeetCode从不订购的客户SQL
    LeetCode超过经理收入的员工SQL
    LeetCode组合两个表SQL
  • 原文地址:https://www.cnblogs.com/peterpan507/p/3580275.html
Copyright © 2011-2022 走看看