iOS导航栏

iOS导航栏侧滑失效问题

关于iOS的导航栏, 想必各个iOS开发者都是经常要面对的问题.也是必须熟练掌握的一个技术点.

比较坑的有两方面.
  • 1.一方面是导航栏上的控件位置问题.
  • 2.一方面是导航栏的返回按钮自定义问题.

今天我主要分享一下自己对这个问题的解决方案的看法.首先我们先来看看iOS中如何设置返回按钮.

iOS中设置返回按钮有两种方式.
  • 一种是在上一级控制器配置.(配置backBarButtonItem)
  • 一种是在本控制器配置.(leftBarButtonItem)

前者只能配置文字或者图片.而不能用自定义的View去配置.苹果的官方文档有如下解释

When this navigation item is immediately below the top item in the stack, 
the navigation controller derives the back button for the navigation bar 
from this navigation item. When this property is nil, the navigation item 
uses the value in its title property to create an appropriate back button. If 
you want to specify a custom image or title for the back button, you can 
assign a custom bar button item (with your custom title or image) to this 
property instead. When configuring your bar button item, do not assign a 
custom view to it; the navigation item ignores custom views in the back 
bar button anyway.

当这个属性是nil的是否, 导航栏使用它的title属性创建一个返回按钮.如果你要为
返回按钮自定义一张图片或者文字, 你可以赋值文字或者图片给UIBarButtonItem
对象.但是对于自定义的View, 在backBarButtonItem中会被忽略.

后者可以很方便的自定义返回按钮.

当同时也存在一个致命的缺点, 就是用leftBarButtonItem自定义返回按钮后, 侧滑手势会失效.如下代码:

UIView *redView = [UIView new];
redView.width = redView.height = 100;
UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithCustomView: redView];
self.navigationItem.leftBarButtonItem = item;

具体原因:

这是为什么呢?从iOS7开始, 系统为UINavigationController, 提供了interactivePopGestureRecognizer手势, 用于右滑返回(pop).但是如果使用leftBarButtonItem属性自定义了返回按钮, 就会造成手势失效.要知道具体原因, 我们还要了解, interactivePopGetureRecognizer从手势触发到行为发生, 要经历以下过程.


Paste_Image.png

interactivePopGestureRecogizer还存在, 但没有起作用.是因为delegate里被阻断了没有调用target/action.或者是调用了, 但没有运行动画.
如果我们知道action的名字, 则可以添加一个自定义的滑动手势, 字节调用系统的action.但是API文档没有提供, 所以该action应该是一个私有的API.使用私有API会造成AppStore审核被拒绝, 所以这个思路也不可取.

那么, 我们就要自己实现滑动返回的动画action, 要么自己重写interfactivePopGestureRecognizer手势的delegate以让手势继续下去, 触发系统的action里面对应的动画.

实现方法:

自定义NavigationController, 遵守手势协议.
设置interactivePopGestureRecognizer手势的代理对象为自身(自定义的navigationController自身)

- (void)viewDidLoad { 
[super viewDidLoad];
self.interactivePopGestureRecognizer.delegate = self; 
}

让手势生效

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { 
  if (self.viewControllers.count <= 1 ) { 
      return NO; 
  } 
  return YES; 
}

// 允许同时响应多个手势
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer        
shouldRecognizeSimultaneouslyWithGestureRecognizer:
(UIGestureRecognizer *)otherGestureRecognizer { 
   return YES;
}

禁止响应手势的是否ViewController中scrollView跟着滚动

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer 
shouldBeRequiredToFailByGestureRecognizer:
(UIGestureRecognizer *)otherGestureRecognizer {    
return [gestureRecognizer isKindOfClass:
UIScreenEdgePanGestureRecognizer.class];
}

在push动画发生的时候, 禁止滑动手势, 因为push动作还没完成, 逻辑上是不允许这个是否进行滑动.重写pushViewController:XX方法.

self.interactivePopGestureRecognizer.enabled = NO;

在使用navigationController的viewController里面添加

- (void)viewDidAppear:(BOOL)animated { 
    [super viewDidAppear:animated];     
    self.navigationController.interactivePopGestureRecognizer.enabled = YES;
 }

或者使导航控制器成为自身的代理, 监听Push完成后的ViewController

- (void)navigationController:(UINavigationController *)navigationController       
didShowViewController:(UIViewController *)viewController                    
animated:(BOOL)animate {    
//控制器入栈之后,启用手势识别    
  if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)])        
      self.interactivePopGestureRecognizer.enabled = YES;
}

// 如果要这样做, 同时应该让该导航控制器成为自己的代理
// 在上面的viewDidLoad里添加如下一句代码
self.delegate = self

后续

如果你不想自己实现.这里有一个韩国开发者利用runtime技术写的框架, 解决了该问题.https://github.com/devxoul/SwipeBack.
.但是实际测试中, 发现该框架还有不足之处, 如有遇到使用该框架还是无效的, 可以参照上面的步骤进行更改, 自定义导航控制器.关于导航栏上的控件位置问题, 会在下一篇进行介绍.

谢谢你的阅读,喜欢本文的朋友可以关注“心悦笔记”微信公众号:xinyuebiji 获取更多文章。

文章分类:心悦笔记

猜你喜欢