在ios7之后,苹果推出了手势滑动返回功能,也就是从屏幕左侧向右滑动可返回上一个界面。大大提升了APP在大屏手机和iPad上的操做体验,场景切换更加流畅。作右滑返回手势配置时,可能会遇到的问题:ios
1. 右滑返回手势为何失效?数组
2. 右滑返回手势如何全局开启及怎么避免页面卡死?bash
3. 特定页面停用右滑手势后如何再次开启?markdown
4. 右滑返回手势与滚动视图手势冲突怎么解决?app
5. 全屏右滑返回怎么设置?ide
右滑返回手势失效主要是由于自定义了页面中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后设置。oop
注意:backBarButtonItem只能自定义image和title,不能重写target 或 action,系统会忽略其余的相关设置项。若是硬是须要重写action作一些其余的工做,则须要自定义一个leftBarButtonItem。 系统默认状况下leftBarButtonItem的优先级是要高于backBarButtonItem的,当存在leftBarButtonItem时,自动忽略backBarButtonItem,达到重写backBarButtonItem的目的,但会形成右滑返回手势的响应代理从当前页面被覆盖性移除。同时,系统也提供了leftItemsSupplementBackButton属性来控制backBarButtonItem 是否被 leftBarButtonItem “覆盖”,默认值是NO,若配置leftBarButtonItem,还须要有返回按钮和右滑手势,须要在leftBarButtonItem或leftBarButtonItems后,把leftItemsSupplementBackButton,设置为YES。布局
如左右分页浏览、看视频、看音频、支付等特定页面场景,是“不但愿”用户便捷离开的,或有弹窗提示的需求,也有避免用户误操做的考虑。同时,可能存在右滑返回手势冲突,或右滑返回后可能有音频焦点不能及时释放的问题。怎么作呢?咱们能够经过代码设置停用右滑返回手势,或改用presentViewController方式加载页面。优化
系统的自带的有返回箭头和上级页面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开发都是基于苹果系统的开发,设置系统级全局性的功能时,最好选择系统或在系统的基础上自定义,尽可能少些自觉得是的彻底自定义,少些奇葩设计,好的内容才是一个产品的核心,好的产品体验也是用户留存的粘合剂!