#import "STKNavigationController.h"
@interface STKNavigationController ()<UINavigationControllerDelegate>
@property(nonatomic,weak) UIViewController* currentShowVC;
@end
@implementation STKNavigationController
- (instancetype)initWithRootViewController:(UIViewController *)rootViewController{
if (self = [super initWithRootViewController:rootViewController]) {
STKNavigationController *nav = [super initWithRootViewController:rootViewController];
nav.interactivePopGestureRecognizer.enabled = YES;
nav.interactivePopGestureRecognizer.delegate = self;
nav.delegate = self;
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
// __weak typeof(self) weakSelf = self;
// if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
// self.interactivePopGestureRecognizer.delegate = weakSelf;
// }
}
#pragma mark UINavigationControllerDelegate
-(void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated{
}
-(void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated{
if (navigationController.viewControllers.count == 1)
self.currentShowVC = nil;
else
self.currentShowVC = viewController;
}
#pragma mark UIGestureRecognizerDelegate
-(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{
if (gestureRecognizer == self.interactivePopGestureRecognizer) {
return (self.currentShowVC == self.topViewController); //the most important
}
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]] && [otherGestureRecognizer isKindOfClass:[UIScreenEdgePanGestureRecognizer class]]) {
return YES;
} else {
return NO;
}
}
前言:
ios7開始 蘋果增加了頁面 右滑返回的效果;具體的是以UINavigationController為容器的ViewController間右滑切換頁面。
代碼里的設置是:
self.navigationController.interactivePopGestureRecognizer.enabled = YES;(default is YES)
可以看到蘋果給navigationController添加了一個手勢(具體為UIScreenEdgePanGestureRecognizer(邊緣手勢,同樣是ios7以后才有的)),就是利用這個手勢實現(xiàn)的 ios7的側(cè)滑返回。
問題1:
然而事情并非我們想的那么簡單。
1.當我們用系統(tǒng)的UINavigationController,并且也是利用系統(tǒng)的navigateBar的時候,是完全沒有問題的
2.但是當我們沒有用系統(tǒng)的navigateBar或者自定義了返回按鈕的時候,這個時候 右滑返回是失效的。
解決(問題1)辦法:
對于這種失效的情況,考慮到interactivePopGestureRecognizer也有delegate屬性,替換默認的self.navigationController.interactivePopGestureRecognizer.delegate來配置右滑返回的表現(xiàn)也是可行的。
我們可以在主NavigationController中設置一下:
self.navigationController.interactivePopGestureRecognizer.delegate =(id)self
問題2:
但是出現(xiàn)很多問題,比如說在rootViewController的時候這個手勢也可以響應,導致整個程序頁面不響應;push了多層后,快速的觸發(fā)兩次手勢,也會錯亂
解決(問題2)辦法:
@interface NavRootViewController : UINavigationController
@property(nonatomic,weak) UIViewController* currentShowVC;
@end
@implementation NavRootViewController
-(id)initWithRootViewController:(UIViewController )rootViewController
{
NavRootViewController nvc = [super initWithRootViewController:rootViewController];
self.interactivePopGestureRecognizer.delegate = self;
nvc.delegate = self;
return nvc;
}
-(void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
}
-(void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
if (navigationController.viewControllers.count == 1)
self.currentShowVC = Nil;
else
self.currentShowVC = viewController;
}
-(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
if (gestureRecognizer == self.interactivePopGestureRecognizer) {
return (self.currentShowVC == self.topViewController); //the most important
}
return YES;
}
@end
借鑒了別人的方法:具體是通過 獲取當前pushView棧里的當前顯示的VC,根據(jù)這個VC來決定 是否開啟手勢(如果currentShowVC 是當前顯示的,則開啟手勢;如果 currentShowVC為nil,則代表在主頁面,關(guān)閉手勢)
注:
當時試了一種方法 就是滑動的時候來回設置 interactivePopGestureRecognizer的delegate;發(fā)現(xiàn) 會有crash,原因就是 因為 我把 當前顯示的VC設置為了這個手勢的delegate,但當這個VC消失的時候,這個delegate便被釋放了,導致crash
至此,覺得ios7上的右滑返回大功告成了,心里正happy,媽蛋,發(fā)現(xiàn)了一個可恥的bug:
UIScrollView 上 右滑返回的手勢失靈了,靠?。。。。?!
問題三:
UIScrollView上手勢失靈:
經(jīng)研究,發(fā)現(xiàn)是UIScrollView上已經(jīng)添加了 panGestureRecognizer(滑動)手勢
ios7 <wbr>側(cè)滑返回
解決(問題三)辦法:
參考:http://www.cnblogs.com/lexingyu/p/3702742.html
【解決方案】
蘋果以UIGestureRecognizerDelegate的形式,支持多個UIGestureRecognizer共存。其中的一個方法是:
1 // called when the recognition of one of gestureRecognizer or otherGestureRecognizer would be blocked by the other
2 // 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 YES
5
(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer;
一句話總結(jié)就是此方法返回YES時,手勢事件會一直往下傳遞,不論當前層次是否對該事件進行響應。
@implementation UIScrollView (AllowPanGestureEventPass)(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]
&& [otherGestureRecognizer isKindOfClass:[UIScreenEdgePanGestureRecognizer class]])
{
return YES;
}
else
{
return NO;
}
}
事實上,對UIGestureRecognizer來說,它們對事件的接收順序和對事件的響應是可以分開設置的,即存在接收鏈和響應鏈。接收鏈如上文所述,和UIView綁定,由UIView的層次決定接收順序。
而響應鏈在apple君的定義下,邏輯出奇的簡單,只有一個方法可以設置多個gestureRecognizer的響應關(guān)系:
// 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都是一個有限狀態(tài)機,上述方法會在兩個gestureRecognizer間建立一個依托于state的依賴關(guān)系,當被依賴的gestureRecognizer.state = failed時,另一個gestureRecognizer才能對手勢進行響應。
所以,只需要
[_scrollView.panGestureRecognizer requireGestureRecognizerToFail:screenEdgePanGestureRecognizer];(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;
}
參考:
牛逼 解決了iOS7下滑動返回與ScrollView共存
http://www.cnblogs.com/lexingyu/p/3702742.html
更牛逼 解決了interactivePopGestureRecognizer.delegate 的 釋放導致crash的問題
http://www.2cto.com/kf/201401/272886.htm
\\\\\\\\\\\\\\\\\\\\\\\\\
iOS 7中在傳統(tǒng)的左上角返回鍵之外,提供了右滑返回上一級界面的手勢。支持此手勢的是UINavigationController中新增的屬性
interactivePopGestureRecognizer,即右滑返回只支持以UINavigationController為容器的ViewController間切換,要想在自定義容器中使用,需要一些額外的工作。
基本地,控制ViewController是否啟用右滑返回,只需要這樣:
1 self.navigationController.interactivePopGestureRecognizer.enabled = YES;
默認情況下enabled為YES。
在實際使用中,遇到了一些問題,整理如下:
1、自定義返回按鈕后,右滑返回失效;
解決方案:比較直觀的辦法是在自定義返回按鈕時,使用backBarButtonItem:
1 UIButton *backButton = [UIButton buttonWithType:UIButtonTypeCustom];
2 //some initialize code here...
3 UIBarButtonItem *barItem = [[UIBarButtonItem alloc] initWithCustomView:backButton];
4 self.navigationItem.leftBarButtonItem = barItem; //not working
5 self.navigationItem.backBarButtonItem = barItem; //serve well
P.S:關(guān)于backBarButtonItem和leftBarButtonItem的區(qū)別:
http://www.cocoachina.com/ask/questions/show/97110
但這樣無法支持左上角多個按鈕的情況??紤]到 interactivePopGestureRecognizer也有delegate屬性, 替換默認的 self . navigationController .interactivePopGestureRecognizer.delegate來配置右滑返回的表現(xiàn)也是可行的。在主ViewController中:
1 self.navigationController.interactivePopGestureRecognizer.delegate = self;
1 - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
2 {
3 if (self.navigationController.viewControllers.count == 1)//關(guān)閉主界面的右滑返回
4 {
5 return NO;
6 }
7 else
8 {
9 return YES;
10 }
11 }
如此做的好處是可以在主ViewController中配置棧中所有ViewController右滑返回的開啟,而不需要在各個ViewController中分別設置enabled。
值得注意的是:在替換了delegate之后,必須在gestureRecognizerShouldBegin:中設置某ViewController A開啟右滑返回,同時在A中未設置interactivePopGestureRecognizer.enabled = NO,右滑返回才會開啟,即二者中任一為NO,右滑返回都處于關(guān)閉狀態(tài)。
2、主界面(UINavigationController棧中的第一個ViewController)默認也是開啟右滑返回的。若在主界面上右滑,不會有動作執(zhí)行。但此時想進入下一級ViewController(如點擊tableView中某一行),切換動畫卻沒有出現(xiàn)。切回桌面再進入應用,發(fā)現(xiàn)直接進入了下一級ViewController。
解決方案:這個問題是在最初試驗右滑返回的使用方式時出現(xiàn)的。在使用自定義返回按鈕的ViewController中
1 self.navigationController.interactivePopGestureRecognizer.delegate = self;
解決解決問題1的同時,造成了問題2。和1中相似,都是在替換了默認的delegate之后,interactivePopGestureRecognizer就能調(diào)用自定義的返回方法了。具體原因尚不清楚,待更新【Mark】。
3、在使用右滑返回拖動到一半時,有時會在導航欄上看到三個排成一行的小藍點。
解決方案:原因不明,解決方案不明。
P.S:在一個帖子上看到一個辦法:
1 self.navigationItem.title = @"";
可以隱藏小藍點,但由于小藍點非必現(xiàn),在不明究竟的情況下很難說是否有效。
帖子鏈接: http://www.tuicool.com/articles/FB3IJ3
(1)在工程中查看, self . navigationController .interactivePopGestureRecognizer.delegate實際上是一個
_UINavigationInteractiveTransition實例,該類聲明如下:
1 @class UIScreenEdgePanGestureRecognizer;
2
3 @interface _UINavigationInteractiveTransition : _UINavigationInteractiveTransitionBase {
4 UIScreenEdgePanGestureRecognizer *_edgePanRecognizer;
5 }
6
7 @property(readonly) UIScreenEdgePanGestureRecognizer * screenEdgePanGestureRecognizer;
8
9 - (void)_configureNavigationGesture;
10 - (BOOL)_gestureRecognizer:(id)arg1 shouldBeRequiredToFailByGestureRecognizer:(id)arg2;
11 - (void)dealloc;
12 - (BOOL)gestureRecognizer:(id)arg1 shouldReceiveTouch:(id)arg2;
13 - (BOOL)gestureRecognizer:(id)arg1 shouldRecognizeSimultaneouslyWithGestureRecognizer:(id)arg2;
14 - (BOOL)gestureRecognizerShouldBegin:(id)arg1;
15 - (id)gestureRecognizerView;
16 - (id)initWithViewController:(id)arg1 animator:(id)arg2;
17 - (id)screenEdgePanGestureRecognizer;
18 - (void)setNotInteractiveTransition;
19 - (void)startInteractiveTransition;
20
21 @end
可以看到,委托的內(nèi)部,實際上是一個UIScreenEdgePanGestureRecognizer實例在起作用,它是iOS7中引入的一個新類,用于支持某些情況下ViewController間切換的初始化。apple官方文檔中對其的描述很少,如下:
A UIScreenEdgePanGestureRecognizer looks for panning (dragging) gestures that start near an edge of the screen. The system uses screen edge gestures in some cases to initiate view controller transitions. You can use this class to replicate the same gesture behavior for your own actions.
After creating a screen edge pan gesture recognizer, assign an appropriate value to the edges property before attaching the gesture recognizer to your view. You use this property to specify from which edges the gesture may start. This gesture recognizer ignores any touches beyond the first touch.
要在自定義的ViewController容器中支持右滑返回,可能就需要用到它。
(2)目前不少應用還是用的iOS 6.1 SDK,而許多iOS7的用戶對右滑返回的需求非常迫切,因此在iOS 6.1SDK下模擬右滑返回在短時間內(nèi)是有必要的,以下是一個通過在push時截取上級ViewController界面為UIImage作為下一級ViewController的背景的一種實現(xiàn)方式:
作者的本意似乎并不要要模擬右滑返回,但稍作修改就能在結(jié)構(gòu)比較簡單的應用中使用,以下是鏈接:
https://github.com/vinqon/MultiLayerNavigation
P.S:對于一些特殊的需求,如在有ScrollView的界面上(比如瀏覽照片)模擬右滑返回,當滑動到最左邊時即執(zhí)行右滑返回,該類無法滿足,待處理【Mark】。
1、UIScreenEdgePanGestureRecognizer Class Reference
2、_UINavigationInteractiveTransition.h
3、自定義返回按鈕時,iOS7手勢返回遇到的問題
http://www.tuicool.com/articles/FB3IJ3
http://www.tuicool.com/articles/vMfAVv
4、餅狀圖