在ios7之後,蘋果推出了手勢滑動返回功能,也就是從屏幕左側向右滑動可返回上一個界面。大大提升了APP在大屏手機和iPad上的操做體驗,場景切換更加流暢。作右滑返回手勢配置時,可能會遇到的問題:ios
1. 右滑返回手勢爲何失效?數組
2. 右滑返回手勢如何全局開啓及怎麼避免頁面卡死?bash
3. 特定頁面停用右滑手勢後如何再次開啓?app
4. 右滑返回手勢與滾動視圖手勢衝突怎麼解決?ide
5. 全屏右滑返回怎麼設置?工具
右滑返回手勢失效主要是由於自定義了頁面中navigationItem的leftBarButtonItem或leftBarButtonItems,或是self.navigationItem.hidesBackButton = YES;隱藏了返回按鈕,亦或是self.navigationItem.leftItemsSupplementBackButton = NO;,讓咱們來梳理下。 UINavigationItem(Apple文檔)是一個常見的類,然而還有很多開發者對該類瞭解甚少,這裏注重說明下backBarButtonItem、leftBarButtonItem、rightBarButtonItem和leftItemsSupplementBackButton四個屬性。leftBarButtonItem、rightBarButtonItem是在當前頁面設置,並展現在當前頁面的navigationItem上。backBarButtonItem如果在當前頁面設置,卻展現在次級頁面navigationItem上。佈局
好比在AViewController push BViewController時,在A設置了self.navigationItem.backBarButtonItem的title和image,通過試驗發現,這個backBarButtonItem爲BViewController的self.navigationController.navigationBar.backItem.backBarButtonItem。雖然self.navigationController.navigationBar.backItem.backBarButtonItem 是讀寫屬性,可是self.navigationController、self.navigationController.navigationBar、 self.navigationController.navigationBar.backItem,都是readonly屬性,所以backBarButtonItem,只能在AViewController中定義並在Push:BViewController以前進行設置。leftBarButtonItem、rightBarButtonItem能夠在BViewController的ViewDidLoad後設置。優化
注意:backBarButtonItem只能自定義image和title,不能重寫target 或 action,系統會忽略其餘的相關設置項。若是硬是須要重寫action作一些其餘的工做,則須要自定義一個leftBarButtonItem。 系統默認狀況下leftBarButtonItem的優先級是要高於backBarButtonItem的,當存在leftBarButtonItem時,自動忽略backBarButtonItem,達到重寫backBarButtonItem的目的,但會形成右滑返回手勢的響應代理從當前頁面被覆蓋性移除。同時,系統也提供了leftItemsSupplementBackButton屬性來控制backBarButtonItem 是否被 leftBarButtonItem 「覆蓋」,默認值是NO,若配置leftBarButtonItem,還須要有返回按鈕和右滑手勢,須要在leftBarButtonItem或leftBarButtonItems後,把leftItemsSupplementBackButton,設置爲YES。ui
如左右分頁瀏覽、看視頻、看音頻、支付等特定頁面場景,是「不但願」用戶便捷離開的,或有彈窗提示的需求,也有避免用戶誤操做的考慮。同時,可能存在右滑返回手勢衝突,或右滑返回後可能有音頻焦點不能及時釋放的問題。怎麼作呢?咱們能夠經過代碼設置停用右滑返回手勢,或改用presentViewController方式加載頁面。atom
系統的自帶的有返回箭頭和上級頁面title的返回按鈕,咱們無需設置,系統自動生成,默認tintColor爲藍色。然而,這樣的樣式並非咱們想要的。咱們一般作法是去,設置該頁面的leftBarButtonItem或leftBarButtonItems,來自定義返回按鈕的樣式。經過上面的問題分析,咱們能夠知道,leftBarButtonItem或leftBarButtonItems 直接覆蓋了self.navigationController.navigationBar.backItem.backBarButtonItem,形成右滑返回手勢的響應代理從當前頁面被覆蓋性移除,形成右滑返回手勢失效。咱們能夠經過在上個頁面設置self.navigationItem.backBarButtonItem,並在下個頁面設置self.navigationItem.leftItemsSupplementBackButton = YES。沒有作基類管理的項目可能處處都是自定義leftBarButtonItem或leftBarButtonItem,工做量較大。快上車,讓老司機帶你一程!
既然設置backBarButtonItem較爲繁雜,咱們能夠換個思路,手勢已被覆蓋性移除,咱們須要給頁面添加上右滑返回手勢。若項目有全局的UINavigationController基類,實現下列參考代碼:
@implementation YGNavigationController
- (void)viewDidLoad
{
[super viewDidLoad];
//設置右滑返回手勢的代理爲自身
__weak typeof(self) weakself = self;
if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
self.interactivePopGestureRecognizer.delegate = (id)weakself;
}
}
#pragma mark - UIGestureRecognizerDelegate
//這個方法是在手勢將要激活前調用:返回YES容許右滑手勢的激活,返回NO不容許右滑手勢的激活
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
if (gestureRecognizer == self.interactivePopGestureRecognizer) {
//屏蔽調用rootViewController的滑動返回手勢,避免右滑返回手勢引發死機問題
if (self.viewControllers.count < 2 ||
self.visibleViewController == [self.viewControllers objectAtIndex:0]) {
return NO;
}
}
//這裏就是非右滑手勢調用的方法啦,統一容許激活
return YES;
}
複製代碼
將項目中的使用UINavigationController 替換爲UINavigationController基類,自定義返回按鈕設置不變,恢復了右滑返回手勢。注意:導航欄的左側也是支持右滑返回手勢,如有UIViewController基類也能夠參照上面設置代碼調整設置,來消除導航欄的左側小區域的右滑返回。
必定要實現UIGestureRecognizerDelegate 並作rootViewController 判斷,不然,在rootViewController頁面會存在右滑返回死機的問題。
咱們查看UINavigationController 文檔,能夠找到
@property(nullable, nonatomic, readonly) UIGestureRecognizer *interactivePopGestureRecognizer NS_AVAILABLE_IOS(7_0) __TVOS_PROHIBITED;
複製代碼
能夠經過設置頁面的VC.navigationController.interactivePopGestureRecognizer.enabled 來控制當前頁面的右滑返回手勢是否可用。咱們能夠建立一個UIViewController 的分類建立兩個類方法。
+ (void)popGestureClose:(UIViewController *)VC
{
// 禁用側滑返回手勢
if ([VC.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
//這裏對添加到右滑視圖上的全部手勢禁用
for (UIGestureRecognizer *popGesture in VC.navigationController.interactivePopGestureRecognizer.view.gestureRecognizers) {
popGesture.enabled = NO;
}
//若開啓全屏右滑,不能再使用下面方法,請對數組進行處理
//VC.navigationController.interactivePopGestureRecognizer.enabled = NO;
}
}
+ (void)popGestureOpen:(UIViewController *)VC
{
// 啓用側滑返回手勢
if ([VC.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
//這裏對添加到右滑視圖上的全部手勢啓用
for (UIGestureRecognizer *popGesture in VC.navigationController.interactivePopGestureRecognizer.view.gestureRecognizers) {
popGesture.enabled = YES;
}
//若開啓全屏右滑,不能再使用下面方法,請對數組進行處理
//VC.navigationController.interactivePopGestureRecognizer.enabled = YES;
}
}
複製代碼
具體怎麼使用呢?咱們須要在停用右滑返回手勢的頁面實現如下兩個方法,通過屢次調試驗證,必須是如下兩個方法。停用當前頁面後,不影響上級頁面和下級頁面的右滑返回。
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[UIViewController popGestureClose:self];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[UIViewController popGestureOpen:self];
}
複製代碼
網上的思路大可能是基於方案一,這是我在研究方案一中回溯思路得出的一個方案,直接利用系統的backBarButtonItem和右滑返回手勢特性,相對更穩定,更高效,我想iOS系統APP的右滑返回設計應是這個「官方思路」。
這裏須要對每一個頁面設置本身的backBarButtonItem,就像設置每一個頁面的leftBarButtonItem的思路同樣。可是backBarButtonItem是一個特殊的按鈕,能夠說只響應頁面的返回和銷燬,表現爲只能自定義image和title,不能重寫target 或 action。來讓咱們自定義如下backBarButtonItem。參照問題分析的思路,須在AViewController中實現下列參考代碼:
UIBarButtonItem *backItem = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil];
//自定義返回按鈕的視圖,如細化返回圖標。
[self.navigationController.navigationBar setBackIndicatorImage:[UIImage imageNamed:@"navi_back_icon"]];
[self.navigationController.navigationBar setBackIndicatorTransitionMaskImage:[UIImage imageNamed:@"navi_back_icon"]];
//設置tintColor 改變自定圖片顏色
self.navigationController.navigationBar.tintColor = [UIColor whiteColor];
//設置自定義的返回按鈕
self.navigationItem.backBarButtonItem = backItem;
複製代碼
按照上面的建立思路,已經完成頁面自定義返回按鈕,並保留了右滑返回手勢(注意:導航欄的左側是不僅支持右滑返回手勢,這裏和方案一有一點區別)。在AViewController push BViewController 或 CViewController 都不須要在再重定義leftBarButtonItem,來實返回按鈕了。依次實現各個控制器的backBarButtonItem,便可完成整個APP的右滑返回手勢功能,固然以上代碼咱們能夠封裝到一個UIViewController基類並在ViewDidLoad方法中來統一設置,或者封裝一個工具方法統一調用,當新的頁面頁面須要不一樣的返回樣式時,在push頁面CViewController以前,從新建立backBarButtonItem覆蓋便可。 **注意:**因系統backBarButtonItem中封裝的UIButton使用的左圖右標題的佈局樣式和一般的UIButton上圖下標題的佈局樣式有必定的差異,形成即便標題爲空,返回按鈕的圖標的位置依然偏左,咱們能夠經過UIBarButtonItem的UIBarButtonSystemItemFixedSpace來調圖標位置或者設置佔位符標題增大手勢響應區域。
怎麼作呢?自定義leftBarButtonItem或leftBarButtonItems,並設置leftItemsSupplementBackButton = YES。參考代碼:
//自定義返回按鈕
UIButton *studySearch = [UIButton buttonWithType:UIButtonTypeCustom];
[studySearch setImage:[UIImage imageNamed:@"study_search"] forState:UIControlStateNormal];
[studySearch sizeToFit];
[studySearch addTarget:self action:@selector(studySearchAction) forControlEvents:UIControlEventTouchUpInside];
UIBarButtonItem *studySearchItem = [[UIBarButtonItem alloc] initWithCustomView:studySearch];
self.navigationItem.leftBarButtonItems = @[studySearchItem];
//是否支持顯示左滑返回按鈕,NO不顯示:leftBarButtonItems覆蓋backBarButtonItem,
//YES顯示:backBarButtonItem 顯示在leftBarButtonItems左側
self.navigationItem.leftItemsSupplementBackButton = YES;
複製代碼
leftItemsSupplementBackButton必須在自定義leftBarButtonItem或leftBarButtonItems後纔有效。
有些項目中的導航欄或導航控制器是徹底自定義的,具體的實現的能夠參照方案一實施,這裏再也不作深刻探究。
方案二不會存在方案一中的卡死現象。iOS系統中,滑動返回手勢實際上是一個UIPanGestureRecognizer,UIScrollView的滑動手勢也是UIPanGestureRecognizer,UIPanGestureRecognizer接收順序和UIView的層次結構是一致的。
UINavigationController.view —> UIViewController.view —> UIScrollView —> Screen and User's finger 複製代碼
原理:UIScrollView(包括子類UITextView、UITableView、UICollectionView)的panGestureRecognizer先接收到手勢事件,直接處理後不在往下傳遞。實際上這就是兩個panGestureRecognizer共存的問題。scrollView的pan手勢會讓系統的pan手勢失效,當UIScrollView(UICollectionView)有多頁的時候也會出現滑動返回失效的狀況,咱們須要在scrollView的位置在初始位置的時候,讓兩個手勢同時啓用。 能夠建立UIScrollView的類別category,而後在此類別中實現如下方法便可:
#import "UIScrollView+PopGesture.h"
@implementation UIScrollView (PopGesture)
//此方法返回YES時,手勢事件會一直往下傳遞,不論當前層次是否對該事件進行響應。
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
if ([self panBack:gestureRecognizer]) {
return YES;
}
return NO;
}
//location_X可本身定義,其表明的是滑動返回距左邊的有效長度
- (BOOL)panBack:(UIGestureRecognizer *)gestureRecognizer
{
//是滑動返回距左邊的有效長度
int location_X = 40;
if (gestureRecognizer == self.panGestureRecognizer) {
UIPanGestureRecognizer *pan = (UIPanGestureRecognizer *)gestureRecognizer;
CGPoint point = [pan translationInView:self];
UIGestureRecognizerState state = gestureRecognizer.state;
if (UIGestureRecognizerStateBegan == state || UIGestureRecognizerStatePossible == state) {
CGPoint location = [gestureRecognizer locationInView:self];
//下面的是隻容許在第一張時滑動返回生效
if (point.x > 0 && location.x < location_X && self.contentOffset.x <= 0) {
return YES;
}
// 這是容許每張圖片均可實現滑動返回
// int temp1 = location.x;
// int temp2 = SCREEN_WIDTH;
// NSInteger XX = temp1 % temp2;
// if (point.x > 0 && XX < location_X) {
// return YES;
// }
}
}
return NO;
}
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
if ([self panBack:gestureRecognizer]) {
return NO;
}
return YES;
}
@end
複製代碼
隨着手機屏幕的變大,原來右滑返回略顯不夠人性化,尤爲是對手小的朋友,如何能愉快的單手玩手機呢。對於app要全屏右滑或保持原生邊緣觸發,各有說辭,這裏不討論其好壞,根據產品須要而定。咱們在方案一的基礎上,建立一個屏幕手勢,添加到原來的self.interactivePopGestureRecognizer.view 右滑返回手勢的視圖上,便是講手勢添加到VC.navigationController.interactivePopGestureRecognizer.view.gestureRecognizers數組中,添加手勢必須在設置代理以前完成。
- (void)viewDidLoad
{
[super viewDidLoad];
//設全屏啓動右滑返回手勢,此處能夠優化爲iPad 上支持全屏
if ((UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)) {
id target = self.interactivePopGestureRecognizer.delegate;
SEL handler = NSSelectorFromString(@"handleNavigationTransition:");
// 獲取添加系統邊緣觸發手勢的View
UIView *targetView = self.interactivePopGestureRecognizer.view;
// 建立pan手勢 做用範圍是全屏
UIPanGestureRecognizer *fullScreenGes = [[UIPanGestureRecognizer alloc]initWithTarget:target action:handler];
fullScreenGes.delegate = self;
[targetView addGestureRecognizer:fullScreenGes];
// 關閉邊緣觸發手勢 防止和原有邊緣手勢衝突(也可不用關閉)
[self.interactivePopGestureRecognizer setEnabled:NO];
}
//設置右滑返回手勢的代理爲自身
__weak typeof(self) weakself = self;
if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
self.interactivePopGestureRecognizer.delegate = (id)weakself;
}
}
複製代碼
注意: 系統在self.interactivePopGestureRecognizer.view上已經添加有VC.navigationController.interactivePopGestureRecognizer手勢,也能夠在VC.navigationController.interactivePopGestureRecognizer.view.gestureRecognizers數組中取出,此時數組中,有兩個響應手勢。所以對方案一中的手勢控制就要使用數組形式的處理方式。
for (UIGestureRecognizer *popGesture in VC.navigationController.interactivePopGestureRecognizer.view.gestureRecognizers) {
popGesture.enabled = NO;
}
複製代碼
iOS開發都是基於蘋果系統的開發,設置系統級全局性的功能時,最好選擇系統或在系統的基礎上自定義,儘可能少些自覺得是的徹底自定義,少些奇葩設計,好的內容纔是一個產品的核心,好的產品體驗也是用戶留存的粘合劑!