一個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開發者的好朋友!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM