最近的一個項目中使用了兩個功能 * `抽屜` * `懸浮按鈕` 這個兩個功能都跟用戶的手勢交互緊密相關 抽屜 * `滑動開關抽屜` * `點擊開關抽屜` 懸浮按鈕 * `拖動按鈕` * `點擊事件` --- ##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. 用戶的手勢交互以UITouch和UIEvent的形式存儲在當前應用程序的事件隊列中。 2. UIApplication單例將對象將事件從隊列的頂部取出,而後派發下去到焦點窗口即擁有當前用戶事件焦點的窗口(當前應用程序窗口UIWindow) 3. UIWindow在把touch傳遞給view上的手勢識別器。 4. 若是手勢識別器識別成功,再也不傳遞給view,識別失敗則傳遞給view 描述概念老是頭疼的,簡單來打個比方,UIWindow就是一個後媽,手勢識別器就是後媽的親兒子,view呢是否是親兒子,後媽每次拿到好吃的都是先分給親兒子先,若是親兒子喜歡吃,就不會再給另一個兒子吃了,不只不讓吃,還會把以前的給的全拿回來,只有親兒子不吃的東西纔會丟給另一個兒子吃。 這裏面涉及了UIGestureRecognizer和Event Handle Method的方法調用,再來一張圖配合理解。  扯了個蛋,本身也理解的通透了,挺好挺好。 ##後記 這篇博客主要記錄了我對於bug發生緣由的記錄,不過在查詢文檔過程當中,還看到了不少其餘相關的內容,不過鑑於時間有限,也無法一會兒記錄下來。等空下來的時候,我會本身再總結一下響應鏈方面的機制。最後不得不說,蘋果文檔真是IOS開發者的好朋友!