標籤:style blog class code java tar javascript color get strong int
【轉載請註明出處】 = =不是整篇複製就算註明出處了親。。。
iOS7下滑動返回與ScrollView共存二三事
【前情回顧】
去年的時候,寫了這篇文章iOS7滑動返回。文中提到,對於多頁面結構的應用,可以替換interactivePopGestureRecognizer的delegate以統一管理應用中所有頁面滑動返回的開關,比如在UINavigationController的衍生類別中
1 //我是一個NavigationController的衍生類別 2 - (id)initWithRootViewController:(UIViewController *)rootViewController 3 { 4 self = [super initWithRootViewController:rootViewController]; 5 if (self) 6 { 7 //在naviVC中統一處理棧中各個vc是否支援滑動返回的情況 8 //當前僅最底層的vc關閉滑動返回 9 self.interactivePopGestureRecognizer.delegate = (id<UIGestureRecognizerDelegate>)self;10 }11 12 return self;13 }
然後在委託中控制滑動返回的開關
1 - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer 2 { 3 if (self.viewControllers.count == 1)//關閉主介面的右滑返回 4 { 5 return NO; 6 } 7 else 8 { 9 return YES;10 }11 }
【問題所在】
看上去挺美好的。。。。直到遇到了ScrollView。
替換了delegate後,在使用時ScrollView,在螢幕左邊緣就無法觸發滑動返回效果了,
【問題原因】
滑動返回事實上也是由存在已久的UIPanGestureRecognizer來識別並響應的,它直接與UINavigationController的view(方便起見,下文中以UINavigationController.view表示)進行綁定,因此中存在如下關係:
UIPanGestureRecognizer ——bind—— UIScrollView
UIScreenEdgePanGestureRecognizer ——bind—— UINavigationController.view
滑動返回無法觸發,說明UIScreenEdgePanGestureRecognizer並沒有接收到手勢事件。
根據apple君的官方文檔,UIGestureRecognizer和UIView是多對一的關係(具體點這裡),UIGestureRecognizer一定要和view進行綁定才能發揮作用。因此不難想象,UIGestureRecognizer對於螢幕上的手勢事件,其接收順序和UIView的階層是一致的。同樣以為例
(我是Z軸)-------------------------------------------------------------------------------------------------------------------------------------->
UINavigationController.view —> UIViewController.view —> UIScrollView —> Screen and User‘s finger
即UIScrollView的panGestureRecognizer先接收到了手勢事件,直接就地處理而沒有往下傳遞。
實際上這就是兩個panGestureRecognizer共存的問題。
【解決方案】
蘋果以UIGestureRecognizerDelegate的形式,支援多個UIGestureRecognizer共存。其中的一個方法是:
1 // called when the recognition of one of gestureRecognizer or otherGestureRecognizer would be blocked by the other2 // return YES to allow both to recognize simultaneously. the default implementation returns NO (by default no two gestures can be recognized simultaneously)3 //4 // note: returning YES is guaranteed to allow simultaneous recognition. returning NO is not guaranteed to prevent simultaneous recognition, as the other gesture‘s delegate may return YES5 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer;
一句話總結就是此方法返回YES時,手勢事件會一直往下傳遞,不論當前層次是否對該事件進行響應。
看看UIScrollView的標頭檔,有如下描述:
1 // Use these accessors to configure the scroll view‘s built-in gesture recognizers.2 // Do not change the gestures‘ delegates or override the getters for these properties.3 @property(nonatomic, readonly) UIPanGestureRecognizer *panGestureRecognizer NS_AVAILABLE_IOS(5_0);
UIScrollView本身是其panGestureRecognizer的delegate,且apple君明確表明不能修改它的delegate(修改的時候也會有警告)。
那麼只好曲線救國。
UIScrollView作為delegate,說明UIScrollView中實現了上文提到的shouldRecognizeSimultaneouslyWithGestureRecognizer方法,返回了NO。建立一個UIScrollView的category,由於category中的同名方法會覆蓋原有.m檔案中的實現,使得可以自訂手勢事件的傳遞,如下:
1 @implementation UIScrollView (AllowPanGestureEventPass) 2 3 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer 4 { 5 if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]] 6 && [otherGestureRecognizer isKindOfClass:[UIScreenEdgePanGestureRecognizer class]]) 7 { 8 return YES; 9 }10 else11 {12 return NO;13 }14 }
再次運行demo,看看效果:
嗯,滑動返回已經成功觸發,鼓掌!
等會等會。。。
好像不太對。。。
scrollView怎麼也滾動了!!!!!
O。。。只是做到了將手勢事件往下傳遞,而沒有關閉掉在邊緣時UIScrollView對事件的響應。
事實上,對UIGestureRecognizer來說,它們對事件的接收順序和對事件的響應是可以分開設定的,即存在接收鏈和響應鏈。接收鏈如上文所述,和UIView綁定,由UIView的層次決定接收順序。
而響應鏈在apple君的定義下,邏輯出奇的簡單,只有一個方法可以設定多個gestureRecognizer的響應關係:
// create a relationship with another gesture recognizer that will prevent this gesture‘s actions from being called until otherGestureRecognizer transitions to UIGestureRecognizerStateFailed// if otherGestureRecognizer transitions to UIGestureRecognizerStateRecognized or UIGestureRecognizerStateBegan then this recognizer will instead transition to UIGestureRecognizerStateFailed// example usage: a single tap may require a double tap to fail- (void)requireGestureRecognizerToFail:(UIGestureRecognizer *)otherGestureRecognizer;
每個UIGesturerecognizer都是一個有限狀態機器,上述方法會在兩個gestureRecognizer間建立一個依託於state的依賴關係,當被依賴的gestureRecognizer.state = failed時,另一個gestureRecognizer才能對手勢進行響應。
所以,只需要
[_scrollView.panGestureRecognizer requireGestureRecognizerToFail:screenEdgePanGestureRecognizer];
即可。
再次運行demo,看看效果:
Works like a Charm!!
P.S:screenEdgePanGestureRecognizer是和UINavigationController.view綁定的,因此可以遍曆UINavigationController.view.gestureRecognizers來擷取,如下:
- (UIScreenEdgePanGestureRecognizer *)screenEdgePanGestureRecognizer{ UIScreenEdgePanGestureRecognizer *screenEdgePanGestureRecognizer = nil; if (self.view.gestureRecognizers.count > 0) { for (UIGestureRecognizer *recognizer in self.view.gestureRecognizers) { if ([recognizer isKindOfClass:[UIScreenEdgePanGestureRecognizer class]]) { screenEdgePanGestureRecognizer = (UIScreenEdgePanGestureRecognizer *)recognizer; break; } } } return screenEdgePanGestureRecognizer;}
demo地址:https://github.com/cDigger/CoExistOfScrollViewAndBackGesture/tree/master
【總結】
寫了這麼多,只是為了最初統一管理滑動返回的一點點便利,似乎很有些得不償失。
我並不建議直接在項目中使用這種非常規手段。
但使用apple君提供的積木,自己拼出系統中的新功能,也是iOS開發的樂趣之一啊。