序言

在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,工作量较大。快上车,让老司机带你一程!

保留系统的右滑返回手势

既然设置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右滑返回手势全解和最佳实施方案相关推荐

  1. ios开发返回按钮消失_iOS开发之自定义导航栏返回按钮右滑返回手势失效的解决...

    我相信针对每一个iOS开发者来说~除了根视图控制器外~所有的界面通过导航栏push过去的界面都是可以通过右滑来返回上一个界面~其实~在很多应用和APP中~用户已经习惯了这个功能~然而~作为开发者的我们 ...

  2. App开发流程之右滑返回手势功能续

    上一篇记录了利用系统私有变量和方法实现右滑返回手势功能:http://www.cnblogs.com/ALongWay/p/5893515.html 这篇继续记录另一种方案:利用UINavigatio ...

  3. 苹果侧边滑动返回_iOS系统右滑返回手势问题及解决方案

    在iOS7之后,苹果推出了手势滑动返回功能,也就是从屏幕左侧向右滑动可返回上一个界面.大大提高了APP在大屏手机和iPad上的操作体验,场景切换更加流畅. 常见的问题有: 1.右滑手势失效 2.右滑手 ...

  4. 禁用导航栏的右滑返回实现全屏手势返回

    今天发现项目中push 的也面的右滑都无法pop 查阅相关资料发现 导航栏右滑手势失效基本有两种情况 1: self.navigationController.interactivePopGestur ...

  5. iOS 右滑返回失效问题终极解决方案

    iOS 的右滑返回是必不可少的一项功能,否则用户体验会大打折扣,但是会经常会碰到某些页面右滑返回失效的情况,下面记录一下解决各种情况下右滑返回失效的方法: 1. 自定义返回按钮 如果页面上是自定义的返 ...

  6. ios7自定义返回按钮后,右滑返回功能失效解决方法

    ios7自定义返回按钮后,右滑返回功能失效解决方法 -(void)viewWillAppear:(BOOL)animated{ [super viewWillAppear:animated]; //开 ...

  7. iOS 右滑手势返回上一级

    首先iOS7以后系统默认自带了侧滑功能,当用户在界面的左边滑动的时候,就会有侧滑功能. 但是如果我们从从导航控制器的返回按钮,就发现系统所带的侧滑返回功能无法使用,而且有些功能不尽人意.系统自定义的优 ...

  8. iOS 为自定义返回按钮的页面添加右滑返回

    2019独角兽企业重金招聘Python工程师标准>>> 苹果一直都在人机交互中尽力做到极致,在iOS7中,新增加了一个小小的功能,也就是这个api:self.navigationCo ...

  9. android右滑返回动画,Android仿微信右滑返回功能的实例代码

    先上效果图,如下: 先分析一下功能的主要技术点,右滑即手势判断,当滑到一直距离时才执行返回,并且手指按下的位置是在屏幕的最左边(这个也是有一定范围的),  这些可以实现onTouchEvent来实现. ...

最新文章

  1. 读懂深度迁移学习,看这文就够了 | 赠书
  2. SQLServer查询死锁
  3. 面试必问:常用的加密算法有哪些?
  4. Fabricjs对Canvas画布和对象的事件监听
  5. 微软向Linux社区开放60000多项专利:对开源微软是认真的
  6. 增加数据库控制文件命令
  7. java mvc引擎_SpringMvc+JavaConfig+Idea 搭建项目
  8. telegram 机器人_学习使用Python在Telegram中构建您的第一个机器人
  9. mysql table as_Mysql中create table as与like命令的区别
  10. Java不支持创建范型数组分析
  11. vcpkg:使用 vcpkg
  12. 毕啸南专栏 | 对话阿里王坚:数据价值是新大陆,新技术将重构互联网
  13. 2022《福布斯》富豪榜发布,FTX联合创始人Gary Wang上榜
  14. 手机开发APP整体界面设计工具之墨刀---没用过就知道它很牛掰
  15. 数字统计之统计页码数字出现的次数
  16. 如何画好架构图详解(建议收藏)
  17. GOM登录器配置免费生成图文教程
  18. python throw_python 之 异常处理
  19. java多线程---重入锁ReentrantLock
  20. 迭代器模式(lterator Pattern)简介

热门文章

  1. 由筷子被嘲讽来谈AM中的【价值观】
  2. 项目质量管理工具--鱼骨图(石川图)
  3. Android 高仿豌豆荚 一键安装app 功能 实现
  4. BZOJ 1135: [POI2009]Lyz
  5. Navicat导出整个数据库
  6. Linux环境下——C语言聊天室项目
  7. NLP领域表达退化各向异性理解及对应策略总结
  8. 利用FDTD软件仿真拓扑光子(二)-光子晶体结构分析
  9. 618大促,我把知识星球的价格调错了……
  10. 五、入门python第五课