序言
???在iOS7之后嘁圈,蘋果推出了手勢滑動返回功能,也就是從屏幕左側(cè)向右滑動可返回上一個界面蟀淮。大大提高了APP在大屏手機(jī)和iPad上的操作體驗(yàn)最住,場景切換更加流暢。做右滑返回手勢配置時怠惶,可能會遇到的問題:
???1. 右滑返回手勢為什么失效?
???2. 右滑返回手勢如何全局開啟及怎么避免頁面卡死涨缚?
???3. 特定頁面停用右滑手勢后如何再次開啟?
???4. 右滑返回手勢與滾動視圖手勢沖突怎么解決?
???5. 全屏右滑返回怎么設(shè)置?
問題分析
右滑返回手勢為什么失效?
???右滑返回手勢失效主要是因?yàn)樽远x了頁面中navigationItem的leftBarButtonItem或leftBarButtonItems策治,或是self.navigationItem.hidesBackButton = YES;隱藏了返回按鈕脓魏,亦或是self.navigationItem.leftItemsSupplementBackButton = NO;,讓我們來梳理下通惫。
???UINavigationItem(Apple文檔)是一個常見的類轧拄,然而還有不少開發(fā)者對該類了解甚少,這里注重說明下backBarButtonItem讽膏、leftBarButtonItem檩电、rightBarButtonItem和leftItemsSupplementBackButton四個屬性。leftBarButtonItem、rightBarButtonItem是在當(dāng)前頁面設(shè)置俐末,并展示在當(dāng)前頁面的navigationItem上料按。backBarButtonItem若是在當(dāng)前頁面設(shè)置,卻展示在次級頁面navigationItem上卓箫。
???比如在AViewController push BViewController時载矿,在A設(shè)置了self.navigationItem.backBarButtonItem的title和image,經(jīng)過測試發(fā)現(xiàn)烹卒,這個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之前進(jìn)行設(shè)置蝙泼。leftBarButtonItem谣辞、rightBarButtonItem可以在BViewController的ViewDidLoad后設(shè)置迫摔。
???注意:backBarButtonItem只能自定義image和title,不能重寫target 或 action泥从,系統(tǒng)會忽略其他的相關(guān)設(shè)置項(xiàng)句占。如果硬是需要重寫action做一些其他的工作,則需要自定義一個leftBarButtonItem躯嫉。
???系統(tǒng)默認(rèn)情況下leftBarButtonItem的優(yōu)先級是要高于backBarButtonItem的辖众,當(dāng)存在leftBarButtonItem時,自動忽略backBarButtonItem和敬,達(dá)到重寫backBarButtonItem的目的凹炸,但會造成右滑返回手勢的響應(yīng)代理從當(dāng)前頁面被覆蓋性移除。同時昼弟,系統(tǒng)也提供了leftItemsSupplementBackButton屬性來控制backBarButtonItem 是否被 leftBarButtonItem “覆蓋”啤它,默認(rèn)值是NO,若配置leftBarButtonItem舱痘,還需要有返回按鈕和右滑手勢变骡,需要在leftBarButtonItem或leftBarButtonItems后,把leftItemsSupplementBackButton芭逝,設(shè)置為YES塌碌。
特定頁面停用右滑手勢?
???如左右分頁瀏覽旬盯、看視頻台妆、看音頻翎猛、支付等特定頁面場景,是“不希望”用戶便捷離開的接剩,或有彈窗提示的需求切厘,也有避免用戶誤操作的考慮。同時懊缺,可能存在右滑返回手勢沖突疫稿,或右滑返回后可能有音頻焦點(diǎn)不能及時釋放的問題。怎么做呢鹃两?我們可以通過代碼設(shè)置停用右滑返回手勢遗座,或改用presentViewController方式加載頁面。
恢復(fù)右滑手勢的解決方案
方案一 手勢代理替換
???系統(tǒng)自帶返回箭頭和上級頁面title的返回按鈕俊扳,我們無需設(shè)置途蒋,系統(tǒng)自動生成,默認(rèn)tintColor為藍(lán)色拣度。然而碎绎,這樣的樣式并不是我們想要的螃壤。我們通常做法是去設(shè)置該頁面的leftBarButtonItem或leftBarButtonItems抗果,來自定義返回按鈕的樣式。通過上面的問題分析可知leftBarButtonItem或leftBarButtonItems 會直接覆蓋self.navigationController.navigationBar.backItem.backBarButtonItem奸晴,造成右滑返回手勢響應(yīng)代理從當(dāng)前頁面被覆蓋性移除冤馏,造成右滑返回手勢失效。我們可以通過在上個頁面設(shè)置self.navigationItem.backBarButtonItem寄啼,并在下個頁面設(shè)置self.navigationItem.leftItemsSupplementBackButton = YES逮光,來開啟右滑返回手勢功能。沒有做基類管理的項(xiàng)目可能到處都是自定義leftBarButtonItem或leftBarButtonItem墩划,適配工作量較大涕刚。別擔(dān)心,讓老司機(jī)帶你一程乙帮!
保留系統(tǒng)的右滑返回手勢
???既然設(shè)置backBarButtonItem較為繁雜杜漠,我們可以換個思路,手勢已被覆蓋性移除察净,我們需要給頁面添加上右滑返回手勢驾茴。若項(xiàng)目有全局的UINavigationController基類,實(shí)現(xiàn)下列參考代碼:
@implementation YGNavigationController
- (void)viewDidLoad
{
[super viewDidLoad];
//設(shè)置右滑返回手勢的代理為自身
__weak typeof(self) weakself = self;
if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
self.interactivePopGestureRecognizer.delegate = (id)weakself;
}
}
#pragma mark - UIGestureRecognizerDelegate
//這個方法是在手勢將要激活前調(diào)用:返回YES允許右滑手勢的激活氢卡,返回NO不允許右滑手勢的激活
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
if (gestureRecognizer == self.interactivePopGestureRecognizer) {
//屏蔽調(diào)用rootViewController的滑動返回手勢锈至,避免右滑返回手勢引起死機(jī)問題
if (self.viewControllers.count < 2 ||
self.visibleViewController == [self.viewControllers objectAtIndex:0]) {
return NO;
}
}
//這里就是非右滑手勢調(diào)用的方法啦,統(tǒng)一允許激活
return YES;
}
???將項(xiàng)目中的使用UINavigationController 替換為UINavigationController基類译秦,自定義返回按鈕設(shè)置不變峡捡,恢復(fù)了右滑返回手勢击碗。注意:導(dǎo)航欄的左側(cè)也是支持右滑返回手勢,若有UIViewController基類也可以參照上面設(shè)置代碼調(diào)整設(shè)置棋返,來消除導(dǎo)航欄的左側(cè)小區(qū)域的右滑返回延都。
???一定要實(shí)現(xiàn)UIGestureRecognizerDelegate 并做rootViewController 判斷,否則睛竣,在rootViewController頁面會存在右滑返回死機(jī)的問題晰房。
特定頁面停用右滑手勢
???我們查看UINavigationController 文檔,可以找到
@property(nullable射沟, nonatomic殊者, readonly) UIGestureRecognizer *interactivePopGestureRecognizer NS_AVAILABLE_IOS(7_0) __TVOS_PROHIBITED;
???可以通過設(shè)置頁面的VC.navigationController.interactivePopGestureRecognizer.enabled 來控制當(dāng)前頁面的右滑返回手勢是否可用。我們可以創(chuàng)建一個UIViewController 的分類創(chuàng)建兩個類方法验夯。
+ (void)popGestureClose:(UIViewController *)VC
{
// 禁用側(cè)滑返回手勢
if ([VC.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
//這里對添加到右滑視圖上的所有手勢禁用
for (UIGestureRecognizer *popGesture in VC.navigationController.interactivePopGestureRecognizer.view.gestureRecognizers) {
popGesture.enabled = NO;
}
//若開啟全屏右滑猖吴,不能再使用下面方法,請對數(shù)組進(jìn)行處理
//VC.navigationController.interactivePopGestureRecognizer.enabled = NO;
}
}
+ (void)popGestureOpen:(UIViewController *)VC
{
// 啟用側(cè)滑返回手勢
if ([VC.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
//這里對添加到右滑視圖上的所有手勢啟用
for (UIGestureRecognizer *popGesture in VC.navigationController.interactivePopGestureRecognizer.view.gestureRecognizers) {
popGesture.enabled = YES;
}
//若開啟全屏右滑挥转,不能再使用下面方法海蔽,請對數(shù)組進(jìn)行處理
//VC.navigationController.interactivePopGestureRecognizer.enabled = YES;
}
}
???具體怎么使用呢?我們需要在停用右滑返回手勢的頁面實(shí)現(xiàn)以下兩個方法绑谣,經(jīng)過多次調(diào)試驗(yàn)證党窜,必須是以下兩個方法。停用當(dāng)前頁面后借宵,不影響上級頁面和下級頁面的右滑返回幌衣。
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[UIViewController popGestureClose:self];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[UIViewController popGestureOpen:self];
}
方案二 原生態(tài):自定義backBarButtonItem
???網(wǎng)上的思路大多是基于方案一,這是我在研究方案一中回溯思路得出的一個方案壤玫,直接利用系統(tǒng)的backBarButtonItem和右滑返回手勢特性豁护,相對更穩(wěn)定,更高效欲间,我想iOS系統(tǒng)APP的右滑返回設(shè)計(jì)應(yīng)是這個“官方思路”楚里。
保留系統(tǒng)的右滑返回手勢
???這里需要對每個頁面設(shè)置自己的backBarButtonItem,就像設(shè)置每個頁面的leftBarButtonItem的思路一樣猎贴。但是backBarButtonItem是一個特殊的按鈕班缎,可以說只響應(yīng)頁面的返回和銷毀,表現(xiàn)為只能自定義image和title嘱能,不能重寫target 或 action吝梅。來讓我們自定義以下backBarButtonItem。參照問題分析的思路惹骂,須在AViewController中實(shí)現(xiàn)下列參考代碼:
UIBarButtonItem *backItem = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil];
//自定義返回按鈕的視圖装蓬,如細(xì)化返回圖標(biāo)。
[self.navigationController.navigationBar setBackIndicatorImage:[UIImage imageNamed:@"navi_back_icon"]];
[self.navigationController.navigationBar setBackIndicatorTransitionMaskImage:[UIImage imageNamed:@"navi_back_icon"]];
//設(shè)置tintColor 改變自定圖片顏色
self.navigationController.navigationBar.tintColor = [UIColor whiteColor];
//設(shè)置自定義的返回按鈕
self.navigationItem.backBarButtonItem = backItem;
???按照上面的創(chuàng)建思路纱扭,已經(jīng)完成頁面自定義返回按鈕牍帚,并保留了右滑返回手勢(注意:導(dǎo)航欄的左側(cè)是不只支持右滑返回手勢,這里和方案一有一點(diǎn)區(qū)別)乳蛾。在AViewController push BViewController 或 CViewController 都不需要在再重定義leftBarButtonItem暗赶,來實(shí)返回按鈕了。依次實(shí)現(xiàn)各個控制器的backBarButtonItem肃叶,即可完成整個APP的右滑返回手勢功能蹂随,當(dāng)然以上代碼我們可以封裝到一個UIViewController基類并在ViewDidLoad方法中來統(tǒng)一設(shè)置,或者封裝一個工具方法統(tǒng)一調(diào)用因惭,當(dāng)新的頁面頁面需要不同的返回樣式時岳锁,在push頁面CViewController之前,重新創(chuàng)建backBarButtonItem覆蓋即可蹦魔。
???注意:因系統(tǒng)backBarButtonItem中封裝的UIButton使用的左圖右標(biāo)題的布局樣式和通常的UIButton上圖下標(biāo)題的布局樣式有一定的差別激率,造成即使標(biāo)題為空,返回按鈕的圖標(biāo)的位置依然偏左勿决,我們可以通過UIBarButtonItem的UIBarButtonSystemItemFixedSpace來調(diào)圖標(biāo)位置或者設(shè)置占位符標(biāo)題增大手勢響應(yīng)區(qū)域乒躺。
特定頁面停用右滑手勢或左側(cè)新添按鈕
???怎么做呢?自定義leftBarButtonItem或leftBarButtonItems剥险,并設(shè)置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左側(cè)
self.navigationItem.leftItemsSupplementBackButton = YES;
???leftItemsSupplementBackButton必須在自定義leftBarButtonItem或leftBarButtonItems后才有效。
方案三 完全自定義導(dǎo)航欄
???有些項(xiàng)目中的導(dǎo)航欄或?qū)Ш娇刂破魇峭耆远x的控乾,具體的實(shí)現(xiàn)的可以參照方案一實(shí)施么介,這里不再做深入探究。
右滑返回引起手勢的沖突
???方案二不會存在方案一中的卡死現(xiàn)象蜕衡。iOS系統(tǒng)中壤短,滑動返回手勢其實(shí)是一個UIPanGestureRecognizer,UIScrollView的滑動手勢也是UIPanGestureRecognizer慨仿,UIPanGestureRecognizer接收順序和UIView的層次結(jié)構(gòu)是一致的久脯。
UINavigationController.view —> UIViewController.view —> UIScrollView —> Screen and User's finger
???原理:UIScrollView(包括子類UITextView、UITableView镰吆、UICollectionView)的panGestureRecognizer先接收到手勢事件帘撰,直接處理后不在往下傳遞。實(shí)際上這就是兩個panGestureRecognizer共存的問題万皿。scrollView的pan手勢會讓系統(tǒng)的pan手勢失效摧找,當(dāng)UIScrollView(UICollectionView)有多頁的時候也會出現(xiàn)滑動返回失效的情況核行,我們需要在scrollView的位置在初始位置的時候,讓兩個手勢同時啟用蹬耘。
可以創(chuàng)建UIScrollView的類別category芝雪,然后在此類別中實(shí)現(xiàn)以下方法即可:
#import "UIScrollView+PopGesture.h"
@implementation UIScrollView (PopGesture)
//此方法返回YES時,手勢事件會一直往下傳遞综苔,不論當(dāng)前層次是否對該事件進(jìn)行響應(yīng)惩系。
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
if ([self panBack:gestureRecognizer]) {
return YES;
}
return NO;
}
//location_X可自己定義,其代表的是滑動返回距左邊的有效長度
- (BOOL)panBack:(UIGestureRecognizer *)gestureRecognizer
{
//是滑動返回距左邊的有效長度
int location_X = 50;
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;
}
// 這是允許每張圖片都可實(shí)現(xiàn)滑動返回
// 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
右滑返回的全屏幕設(shè)置
???隨著手機(jī)屏幕的變大如筛,原來右滑返回略顯不夠人性化蛆挫,尤其是iPhone plus,如何能愉快的單手操作APP妙黍,對于APP要實(shí)現(xiàn)全屏右滑或保持原生邊緣觸發(fā)悴侵,各有說辭,這里不討論其好壞拭嫁,根據(jù)產(chǎn)品需要而定可免。我們在方案一的基礎(chǔ)上,創(chuàng)建一個屏幕手勢做粤,添加到原來的self.interactivePopGestureRecognizer.view 右滑返回手勢的視圖上浇借,即是將手勢添加到VC.navigationController.interactivePopGestureRecognizer.view.gestureRecognizers數(shù)組中,添加手勢必須在設(shè)置代理之前完成怕品。
- (void)viewDidLoad
{
[super viewDidLoad];
//設(shè)全屏啟動右滑返回手勢妇垢,此處可以優(yōu)化為iPad 上支持全屏
if ((UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)) {
id target = self.interactivePopGestureRecognizer.delegate;
SEL handler = NSSelectorFromString(@"handleNavigationTransition:");
// 獲取添加系統(tǒng)邊緣觸發(fā)手勢的View
UIView *targetView = self.interactivePopGestureRecognizer.view;
// 創(chuàng)建pan手勢 作用范圍是全屏
UIPanGestureRecognizer *fullScreenGes = [[UIPanGestureRecognizer alloc]initWithTarget:target action:handler];
fullScreenGes.delegate = self;
[targetView addGestureRecognizer:fullScreenGes];
// 關(guān)閉邊緣觸發(fā)手勢 防止和原有邊緣手勢沖突(也可不用關(guān)閉)
[self.interactivePopGestureRecognizer setEnabled:NO];
}
//設(shè)置右滑返回手勢的代理為自身
__weak typeof(self) weakself = self;
if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
self.interactivePopGestureRecognizer.delegate = (id)weakself;
}
}
???注意: 系統(tǒng)在self.interactivePopGestureRecognizer.view上已經(jīng)添加有VC.navigationController.interactivePopGestureRecognizer手勢,也可以在VC.navigationController.interactivePopGestureRecognizer.view.gestureRecognizers數(shù)組中取出肉康,此時數(shù)組中闯估,有兩個響應(yīng)手勢。因此對方案一中的手勢控制就要使用數(shù)組形式的處理方式吼和。
for (UIGestureRecognizer *popGesture in VC.navigationController.interactivePopGestureRecognizer.view.gestureRecognizers) {
popGesture.enabled = NO;
}
總結(jié)
???iOS開發(fā)是基于蘋果系統(tǒng)的開發(fā)涨薪,設(shè)置系統(tǒng)級全局性的功能時,最好選擇系統(tǒng)庫或在系統(tǒng)庫的基礎(chǔ)上自定義炫乓,盡量少些自以為是的完全自定義造輪子刚夺,少些奇葩設(shè)計(jì),好的內(nèi)容才是一個產(chǎn)品的核心末捣,好的產(chǎn)品體驗(yàn)是用戶留存的粘合劑侠姑!
最后留一個問題:
???場景:從購物車購買商品,下單去訂單頁面箩做,付款成功后莽红,去了支付成功頁面若再返回,則不應(yīng)該返回訂單頁面卒茬,應(yīng)該去購物車船老,甚至回到首頁咖熟,若用右滑返回手勢,怎么監(jiān)控事件柳畔,怎么攔截替換響應(yīng)事件馍管,或其他途徑。
???我也在整理這個問題的解決思路薪韩,歡迎留言确沸,一起完善!