【转】iOS右滑返回手势全解和最佳实施方案
生活随笔
收集整理的這篇文章主要介紹了
【转】iOS右滑返回手势全解和最佳实施方案
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
序言
在ios7以后,蘋果推出了手勢滑動返回功能,也就是從屏幕左側向右滑動可返回上一個界面。大大提高了APP在大屏手機和iPad上的操作體驗,場景切換更加流暢。做右滑返回手勢配置時,可能會遇到的 問題: 1. 右滑返回手勢為什么失效? 2. 右滑返回手勢如何全局開啟及怎么避免頁面卡死? 3. 特定頁面停用右滑手勢后如何再次開啟? 4. 右滑返回手勢與滾動視圖手勢沖突怎么解決? 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. 若設置為YES, 在設置leftBarButtonItem后, 將會保留backBarButtonItem以及右滑手勢.特定頁面停用右滑手勢?
如左右分頁瀏覽、看視頻、看音頻、支付等特定頁面場景,是“不希望”用戶便捷離開的,或有彈窗提示的需求,也有避免用戶誤操作的考慮。同時,可能存在右滑返回手勢沖突,或右滑返回后可能有音頻焦點不能及時釋放的問題。怎么做呢?我們可以通過代碼設置停用右滑返回手勢,或改用presentViewController方式加載頁面。自定義leftBarButtonItem之后, 恢復右滑手勢的解決方案
方案一 手勢代理替換
系統的自帶的有返回箭頭和上級頁面title的返回按鈕,我們無需設置,系統自動生成,默認tintColor為藍色。然而,這樣的樣式并不是我們想要的。我們通常做法是去,設置該頁面的leftBarButtonItem或leftBarButtonItems,來自定義返回按鈕的樣式。通過上面的問題分析,我們可以知道,leftBarButtonItem或leftBarButtonItems 直接覆蓋了self.navigationController.navigationBar.backItem.backBarButtonItem,造成右滑返回手勢的響應代理從當前頁面被覆蓋性移除,造成右滑返回手勢失效。沒有做基類管理的項目可能到處都是自定義leftBarButtonItem或leftBarButtonItem,工作量較大??焐宪?#xff0c;讓老司機帶你一程!保留系統的右滑返回手勢
既然設置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(圖片)
網上的思路大多是基于方案一,這是我在研究方案一中回溯思路得出的一個方案,直接利用系統的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; 按照上面的創建思路,已經完成頁面自定義返回按鈕,并保留了右滑返回手勢(注意:導航欄的左側是不支持右滑返回手勢的,這里和方案一有一點區別)。在A push B 或 C 都不需要在再重定義leftBarButtonItem,來實返回按鈕了。依次實現各個控制器的backBarButtonItem,即可完成整個APP的右滑返回手勢功能,當然以上代碼我們可以封裝到一個UIViewController基類并在ViewDidLoad方法中來統一設置,或者封裝一個工具方法統一調用,當新的頁面頁面需要不同的返回樣式時,在push頁面C之前,重新創建backBarButtonItem覆蓋即可。方案三 完全自定義導航欄
有些項目中的導航欄或導航控制器是完全自定義的,具體的實現的可以參照方案一實施,這里不再做深入探究。右滑返回引起手勢的沖突
方案二不會存在方案一中的卡死現象。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 上支持全屏 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]; 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開發都是基于蘋果系統的開發,設置系統級全局性的功能時,最好選擇系統或在系統的基礎上自定義,盡量少些自以為是的完全自定義,少些奇葩設計,好的內容才是一個產品的核心,好的產品體驗也是用戶留存的粘合劑! 原文總結
以上是生活随笔為你收集整理的【转】iOS右滑返回手势全解和最佳实施方案的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 关于最新社区版idea启动项目前端404
- 下一篇: 解决vue3-print-nb打印二维码