【转】iOS右滑返回手势全解和最佳实施方案
生活随笔
收集整理的這篇文章主要介紹了
【转】iOS右滑返回手势全解和最佳实施方案
小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
序言
在ios7以后,蘋果推出了手勢(shì)滑動(dòng)返回功能,也就是從屏幕左側(cè)向右滑動(dòng)可返回上一個(gè)界面。大大提高了APP在大屏手機(jī)和iPad上的操作體驗(yàn),場(chǎng)景切換更加流暢。做右滑返回手勢(shì)配置時(shí),可能會(huì)遇到的 問題: 1. 右滑返回手勢(shì)為什么失效? 2. 右滑返回手勢(shì)如何全局開啟及怎么避免頁面卡死? 3. 特定頁面停用右滑手勢(shì)后如何再次開啟? 4. 右滑返回手勢(shì)與滾動(dòng)視圖手勢(shì)沖突怎么解決? 5. 全屏右滑返回怎么設(shè)置?問題分析
右滑返回手勢(shì)為什么失效?
右滑返回手勢(shì)失效主要是因?yàn)樽远x了頁面中navigationItem的leftBarButtonItem或leftBarButtonItems,或是self.navigationItem.hidesBackButton = YES;隱藏了返回按鈕,亦或是self.navigationItem.leftItemsSupplementBackButton = NO;,讓我們來梳理下。 ??? UINavigationItem(Apple文檔)是一個(gè)常見的類,然而還有不少開發(fā)者對(duì)該類了解甚少,這里注重說明下 backBarButtonItem、 leftBarButtonItem、 rightBarButtonItem和 leftItemsSupplementBackButton四個(gè)屬性。leftBarButtonItem、rightBarButtonItem是在當(dāng)前頁面設(shè)置,并展示在 當(dāng)前頁面的navigationItem上。backBarButtonItem若是在當(dāng)前頁面設(shè)置,卻展示在 次級(jí)頁面navigationItem上。 比如在AViewController push BViewController時(shí),在A設(shè)置了self.navigationItem.backBarButtonItem的title和image,經(jīng)過試驗(yàn)發(fā)現(xiàn),這個(gè)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)會(huì)忽略其他的相關(guān)設(shè)置項(xiàng)。如果硬是需要重寫action做一些其他的工作,則需要自定義一個(gè)leftBarButtonItem。 ???系統(tǒng)默認(rèn)情況下leftBarButtonItem的優(yōu)先級(jí)是要高于backBarButtonItem的,當(dāng)存在leftBarButtonItem時(shí),自動(dòng)忽略backBarButtonItem,達(dá)到重寫backBarButtonItem的目的,但會(huì)造成右滑返回手勢(shì)的響應(yīng)代理從當(dāng)前頁面被覆蓋性移除。同時(shí),系統(tǒng)也提供了leftItemsSupplementBackButton屬性來控制backBarButtonItem 是否和 leftBarButtonItem 并列顯示,默認(rèn)值是NO. 若設(shè)置為YES, 在設(shè)置leftBarButtonItem后, 將會(huì)保留backBarButtonItem以及右滑手勢(shì).特定頁面停用右滑手勢(shì)?
如左右分頁瀏覽、看視頻、看音頻、支付等特定頁面場(chǎng)景,是“不希望”用戶便捷離開的,或有彈窗提示的需求,也有避免用戶誤操作的考慮。同時(shí),可能存在右滑返回手勢(shì)沖突,或右滑返回后可能有音頻焦點(diǎn)不能及時(shí)釋放的問題。怎么做呢?我們可以通過代碼設(shè)置停用右滑返回手勢(shì),或改用presentViewController方式加載頁面。自定義leftBarButtonItem之后, 恢復(fù)右滑手勢(shì)的解決方案
方案一 手勢(shì)代理替換
系統(tǒng)的自帶的有返回箭頭和上級(jí)頁面title的返回按鈕,我們無需設(shè)置,系統(tǒng)自動(dòng)生成,默認(rèn)tintColor為藍(lán)色。然而,這樣的樣式并不是我們想要的。我們通常做法是去,設(shè)置該頁面的leftBarButtonItem或leftBarButtonItems,來自定義返回按鈕的樣式。通過上面的問題分析,我們可以知道,leftBarButtonItem或leftBarButtonItems 直接覆蓋了self.navigationController.navigationBar.backItem.backBarButtonItem,造成右滑返回手勢(shì)的響應(yīng)代理從當(dāng)前頁面被覆蓋性移除,造成右滑返回手勢(shì)失效。沒有做基類管理的項(xiàng)目可能到處都是自定義leftBarButtonItem或leftBarButtonItem,工作量較大。快上車,讓老司機(jī)帶你一程!保留系統(tǒng)的右滑返回手勢(shì)
既然設(shè)置backBarButtonItem較為繁雜,我們可以換個(gè)思路,手勢(shì)已被覆蓋性移除,我們需要給頁面添加上右滑返回手勢(shì)。若項(xiàng)目有全局的UINavigationController基類,實(shí)現(xiàn)下列參考代碼: @implementation YGNavigationController- (void)viewDidLoad { [super viewDidLoad]; //設(shè)置右滑返回手勢(shì)的代理為自身 __weak typeof(self) weakself = self; if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)]) { self.interactivePopGestureRecognizer.delegate = (id)weakself; } }
#pragma mark - UIGestureRecognizerDelegate //這個(gè)方法是在手勢(shì)將要激活前調(diào)用:返回YES允許右滑手勢(shì)的激活,返回NO不允許右滑手勢(shì)的激活 - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { if (gestureRecognizer == self.interactivePopGestureRecognizer) { //屏蔽調(diào)用rootViewController的滑動(dòng)返回手勢(shì),避免右滑返回手勢(shì)引起死機(jī)問題 if (self.viewControllers.count < 2 || self.visibleViewController == [self.viewControllers objectAtIndex:0]) { return NO; } } //這里就是非右滑手勢(shì)調(diào)用的方法啦,統(tǒng)一允許激活 return YES; } 將項(xiàng)目中的使用UINavigationController 替換為UINavigationController基類,自定義返回按鈕設(shè)置不變,恢復(fù)了右滑返回手勢(shì)。注意:導(dǎo)航欄的左側(cè)也是支持右滑返回手勢(shì),若有UIViewController基類也可以參照上面設(shè)置代碼調(diào)整設(shè)置,來消除導(dǎo)航欄的左側(cè)小區(qū)域的右滑返回。 一定要實(shí)現(xiàn)UIGestureRecognizerDelegate 并做rootViewController 判斷,否則,在rootViewController頁面會(huì)存在右滑返回死機(jī)的問題。
特定頁面停用右滑手勢(shì)
我們查看UINavigationController 文檔,可以找到 @property(nullable, nonatomic, readonly) UIGestureRecognizer *interactivePopGestureRecognizer NS_AVAILABLE_IOS(7_0) __TVOS_PROHIBITED; 可以通過設(shè)置頁面的VC.navigationController.interactivePopGestureRecognizer.enabled 來控制當(dāng)前頁面的右滑返回手勢(shì)是否可用。我們可以創(chuàng)建一個(gè)UIViewController 的分類創(chuàng)建兩個(gè)類方法。 + (void)popGestureClose:(UIViewController *)VC { // 禁用側(cè)滑返回手勢(shì) if ([VC.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) { //這里對(duì)添加到右滑視圖上的所有手勢(shì)禁用 for (UIGestureRecognizer *popGesture in VC.navigationController.interactivePopGestureRecognizer.view.gestureRecognizers) { popGesture.enabled = NO; } //若開啟全屏右滑,不能再使用下面方法,請(qǐng)對(duì)數(shù)組進(jìn)行處理 //VC.navigationController.interactivePopGestureRecognizer.enabled = NO; } }+ (void)popGestureOpen:(UIViewController *)VC { // 啟用側(cè)滑返回手勢(shì) if ([VC.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) { //這里對(duì)添加到右滑視圖上的所有手勢(shì)啟用 for (UIGestureRecognizer *popGesture in VC.navigationController.interactivePopGestureRecognizer.view.gestureRecognizers) { popGesture.enabled = YES; } //若開啟全屏右滑,不能再使用下面方法,請(qǐng)對(duì)數(shù)組進(jìn)行處理 //VC.navigationController.interactivePopGestureRecognizer.enabled = YES; } } 具體怎么使用呢?我們需要在停用右滑返回手勢(shì)的頁面實(shí)現(xiàn)以下兩個(gè)方法,經(jīng)過多次調(diào)試驗(yàn)證,必須是以下兩個(gè)方法。停用當(dāng)前頁面后,不影響上級(jí)頁面和下級(jí)頁面的右滑返回。 - (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)上的思路大多是基于方案一,這是我在研究方案一中回溯思路得出的一個(gè)方案,直接利用系統(tǒng)的backBarButtonItem和右滑返回手勢(shì)特性,相對(duì)更穩(wěn)定,更高效,我想iOS系統(tǒng)APP的右滑返回設(shè)計(jì)應(yīng)是這個(gè)“官方思路”。保留系統(tǒng)的右滑返回手勢(shì)
這里需要對(duì)每個(gè)頁面設(shè)置自己的backBarButtonItem,就像設(shè)置每個(gè)頁面的leftBarButtonItem的思路一樣。但是backBarButtonItem是一個(gè)特殊的按鈕,可以說只響應(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)完成頁面自定義返回按鈕,并保留了右滑返回手勢(shì)(注意:導(dǎo)航欄的左側(cè)是不支持右滑返回手勢(shì)的,這里和方案一有一點(diǎn)區(qū)別)。在A push B 或 C 都不需要在再重定義leftBarButtonItem,來實(shí)返回按鈕了。依次實(shí)現(xiàn)各個(gè)控制器的backBarButtonItem,即可完成整個(gè)APP的右滑返回手勢(shì)功能,當(dāng)然以上代碼我們可以封裝到一個(gè)UIViewController基類并在ViewDidLoad方法中來統(tǒng)一設(shè)置,或者封裝一個(gè)工具方法統(tǒng)一調(diào)用,當(dāng)新的頁面頁面需要不同的返回樣式時(shí),在push頁面C之前,重新創(chuàng)建backBarButtonItem覆蓋即可。方案三 完全自定義導(dǎo)航欄
有些項(xiàng)目中的導(dǎo)航欄或?qū)Ш娇刂破魇峭耆远x的,具體的實(shí)現(xiàn)的可以參照方案一實(shí)施,這里不再做深入探究。右滑返回引起手勢(shì)的沖突
方案二不會(huì)存在方案一中的卡死現(xiàn)象。iOS系統(tǒng)中,滑動(dòng)返回手勢(shì)其實(shí)是一個(gè)UIPanGestureRecognizer,UIScrollView的滑動(dòng)手勢(shì)也是UIPanGestureRecognizer,UIPanGestureRecognizer接收順序和UIView的層次結(jié)構(gòu)是一致的。 UINavigationController.view —>? UIViewController.view —>? UIScrollView —>? Screen and User's finger 原理:UIScrollView(包括子類UITextView、UITableView、UICollectionView)的panGestureRecognizer先接收到手勢(shì)事件,直接處理后不在往下傳遞。實(shí)際上這就是兩個(gè)panGestureRecognizer共存的問題。scrollView的pan手勢(shì)會(huì)讓系統(tǒng)的pan手勢(shì)失效,當(dāng)UIScrollView(UICollectionView)有多頁的時(shí)候也會(huì)出現(xiàn)滑動(dòng)返回失效的情況,我們需要在scrollView的位置在初始位置的時(shí)候,讓兩個(gè)手勢(shì)同時(shí)啟用。 可以創(chuàng)建UIScrollView的類別category,然后在此類別中實(shí)現(xiàn)以下方法即可: #import "UIScrollView+PopGesture.h"@implementation UIScrollView (PopGesture)
//此方法返回YES時(shí),手勢(shì)事件會(huì)一直往下傳遞,不論當(dāng)前層次是否對(duì)該事件進(jìn)行響應(yīng)。 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { if ([self panBack:gestureRecognizer]) { return YES; } return NO; }
//location_X可自己定義,其代表的是滑動(dòng)返回距左邊的有效長(zhǎng)度 - (BOOL)panBack:(UIGestureRecognizer *)gestureRecognizer { //是滑動(dòng)返回距左邊的有效長(zhǎng)度 int location_X = 40; if (gestureRecognizer == self.panGestureRecognizer) { UIPanGestureRecognizer *pan = (UIPanGestureRecognizer *)gestureRecognizer; CGPoint point = [pan translationInView:self];? //拖動(dòng)的距離 UIGestureRecognizerState state = gestureRecognizer.state; if (UIGestureRecognizerStateBegan == state || UIGestureRecognizerStatePossible == state) { CGPoint location = [gestureRecognizer locationInView:self]; //手勢(shì)所在的 //下面的是只允許在第一張時(shí)滑動(dòng)返回生效 if (point.x > 0 && location.x < location_X && self.contentOffset.x <= 0) { return YES; } //?? 這是允許每張圖片都可實(shí)現(xiàn)滑動(dòng)返回 //?? 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ī)屏幕的變大,原來右滑返回略顯不夠人性化,尤其是對(duì)手小的朋友,如何能愉快的單手玩手機(jī)呢。對(duì)于app要全屏右滑或保持原生邊緣觸發(fā),各有說辭,這里不討論其好壞,根據(jù)產(chǎn)品需要而定。我們?cè)诜桨敢坏幕A(chǔ)上,創(chuàng)建一個(gè)屏幕手勢(shì),添加到原來的self.interactivePopGestureRecognizer.view 右滑返回手勢(shì)的視圖上,即是講手勢(shì)添加到VC.navigationController.interactivePopGestureRecognizer.view.gestureRecognizers數(shù)組中, 添加手勢(shì)必須在設(shè)置代理之前完成。- (void)viewDidLoad { [super viewDidLoad]; //設(shè)全屏啟動(dòng)右滑返回手勢(shì),此處可以優(yōu)化為iPad 上支持全屏 id target = self.interactivePopGestureRecognizer.delegate; SEL handler = NSSelectorFromString(@"handleNavigationTransition:"); // 獲取添加系統(tǒng)邊緣觸發(fā)手勢(shì)的View UIView *targetView = self.interactivePopGestureRecognizer.view; // 創(chuàng)建pan手勢(shì) 作用范圍是全屏 UIPanGestureRecognizer *fullScreenGes = [[UIPanGestureRecognizer alloc]initWithTarget:target action:handler]; fullScreenGes.delegate = self; [targetView addGestureRecognizer:fullScreenGes]; // 關(guān)閉邊緣觸發(fā)手勢(shì) 防止和原有邊緣手勢(shì)沖突(也可不用關(guān)閉) [self.interactivePopGestureRecognizer setEnabled:NO]; SEL handler = NSSelectorFromString(@"handleNavigationTransition:"); // 獲取添加系統(tǒng)邊緣觸發(fā)手勢(shì)的View UIView *targetView = self.interactivePopGestureRecognizer.view; // 創(chuàng)建pan手勢(shì) 作用范圍是全屏 UIPanGestureRecognizer *fullScreenGes = [[UIPanGestureRecognizer alloc]initWithTarget:target action:handler]; fullScreenGes.delegate = self; [targetView addGestureRecognizer:fullScreenGes]; // 關(guān)閉邊緣觸發(fā)手勢(shì) 防止和原有邊緣手勢(shì)沖突(也可不用關(guān)閉) [self.interactivePopGestureRecognizer setEnabled:NO]; //設(shè)置右滑返回手勢(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手勢(shì),也可以在VC.navigationController.interactivePopGestureRecognizer.view.gestureRecognizers數(shù)組中取出,此時(shí)數(shù)組中,有兩個(gè)響應(yīng)手勢(shì)。因此對(duì)方案一中的手勢(shì)控制就要使用數(shù)組形式的處理方式。 for (UIGestureRecognizer *popGesture in VC.navigationController.interactivePopGestureRecognizer.view.gestureRecognizers) { popGesture.enabled = NO; }
總結(jié)
iOS開發(fā)都是基于蘋果系統(tǒng)的開發(fā),設(shè)置系統(tǒng)級(jí)全局性的功能時(shí),最好選擇系統(tǒng)或在系統(tǒng)的基礎(chǔ)上自定義,盡量少些自以為是的完全自定義,少些奇葩設(shè)計(jì),好的內(nèi)容才是一個(gè)產(chǎn)品的核心,好的產(chǎn)品體驗(yàn)也是用戶留存的粘合劑! 原文總結(jié)
以上是生活随笔為你收集整理的【转】iOS右滑返回手势全解和最佳实施方案的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 关于最新社区版idea启动项目前端404
- 下一篇: 解决vue3-print-nb打印二维码