iOS学习—— UINavigationController的返回按钮与侧滑返回手势的研究

  侧滑返回手势是从iOS7开始增加的一个返回操作,经历了两年时间估计iPhone用户大部分都已经忽略了屏幕左上角那个碍眼的back按钮了。之前在网上搜过有关侧滑手势的技术博客,发现大多比较散乱,甚至有很多都是简单的粘贴复制,并不全面。侧滑返回的操作效果与左上角的back按钮是一样的,所以一起放在这里进行探讨。  

  导航栏左上角的back按钮是附着在UINavigationController的UINavigationBar里自带的一个返回按钮,导航栏自带的back按钮的图层结构如下图所示。一个UINavigationController只会有一个UIBackButtonContentView,但是可以有多个leftBarButtonItem、rightBarButtonItem(leftBarButtonItem、rightBarButtonItem就在下图所示的UIButtonBarStackView图层下),其中backButton与leftBarButtonItem之间的关系和区别在后面我们会讲到。

一 侧滑返回   

  侧滑返回是系统iOS7自带的一种方便用户进行返回操作而推出的一种新功能。在开发过程中,对侧滑返回进行控制非常简单,主要就是启动侧滑手势和禁用侧滑手势。首先,我们来看一下 UINavigationController 的 @property ,可以找到下面这个属性。

@property(nullable, nonatomic, readonly) UIGestureRecognizer *interactivePopGestureRecognizer;

1.1 侧滑开启与关闭

  UINavigationController的interactivePopGestureRecognizer这个属性就是我们的侧滑返回手势,如果你的项目中没有需求要自定义返回按钮(虽然我觉得这并不太可能),那么你所需要的操作就非常简单了,不多说直接上代码。

self.navigationController.interactivePopGestureRecognizer.enabled = YES;  //启用侧滑手势
self.navigationController.interactivePopGestureRecognizer.enabled = NO;   //禁用侧滑手势 

1.2 侧滑使用注意

  侧滑手势在使用中需要注意的一点就是在项目开发中,我们一般是采用的UITabBar + UINavigationController架构,对于每一个UITabBar的item模块,我们都定义一个UINavigationController对该item模块上的viewController进行控制。而在这个模块上,我们有某个或某些viewController需要禁用侧滑手势(一般需要禁用侧滑手势是因为返回或退出当前viewController时需要double confirm,在一些填表的页面比较常见),而其他的viewController则不需要禁用侧滑手势。这时候我们就需要特别小心,因为 self.navigationController.interactivePopGestureRecognizer.enabled = NO; //禁用侧滑手势 是对当前的UINavigationController有效的,所以一旦你在某个界面禁用了侧滑,那么该UINavigationController控制下的所有viewController都会禁用侧滑,这显然是不合理的。提供一个解决方案就是在进入viewController时 - (void)viewDidAppear:(BOOL)animated; 中禁用侧滑手势,然后在离开viewController时 - (void)viewWillDisappear:(BOOL)animated; 开启策划手势。具体代码如下:

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    // 禁用返回手势
    if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
     self.navigationController.interactivePopGestureRecognizer.enabled = NO;
    }
}

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    // 开启返回手势
    if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
     self.navigationController.interactivePopGestureRecognizer.enabled = YES;
    }
}

1.3 侧滑手势的获取

  如果一个页面上有多个手势,我们要如何去获取策划手势,并对其进行操作呢?其实很简单,侧滑手势是一种UIScreenEdgePanGestureRecognizer,所以我们只需要对当前手势的类别进行判断就可以了。具体代码如下:

//获取侧滑返回手势
- (UIScreenEdgePanGestureRecognizer *)screenEdgePanGestureRecognizer
{
    UIScreenEdgePanGestureRecognizer *screenEdgePanGestureRecognizer = nil;
    if (self.view.gestureRecognizers.count > 0)
    {
        for (UIGestureRecognizer *recognizer in self.view.gestureRecognizers)
        {
            if ([recognizer isKindOfClass:[UIScreenEdgePanGestureRecognizer class]])
            {
                screenEdgePanGestureRecognizer = (UIScreenEdgePanGestureRecognizer *)recognizer;
                break;
            }
        }
    }
    return screenEdgePanGestureRecognizer;
}

1.4 UIScrollView与侧滑手势共存问题

  UIScrollView及其子类自带滑动手势,所以如果一个viewController钟有UIScrollView及其子类的view时,侧滑手势影响用户体验效果,此时用户将无法通过侧滑进行返回。因为侧滑返回手势事实上是由存在已久的UIPanGestureRecognizer来识别并响应的,它直接与UINavigationController的view进行绑定,因此在包含UIScrollView的viewController中存在如下关系:

UIPanGestureRecognizer          ——bind——  UIScrollView

UIScreenEdgePanGestureRecognizer ——bind——  UINavigationController.view

  滑动返回无法触发,说明UIScreenEdgePanGestureRecognizer并没有接收到手势事件,也就是说UIScreenEdgePanGestureRecognizer被UIPanGestureRecognizer屏蔽了。因此,我们为了实现侧滑返回手势,我们需要设置两种手势的共存和先后响应问题,我们可以设置UIScrollView的UIPanGestureRecognizer手势在UIScreenEdgePanGestureRecognizer失效时才识别,具体设置方法如下:

//指定滑动手势在侧滑返回手势失效后响应
[self.tableView.panGestureRecognizer requireGestureRecognizerToFail:[self.navigationController screenEdgePanGestureRecognizer]];

 二 导航栏的back按钮

  在了解导航栏的返回按钮之前,我们先了解一下导航栏管理导航栏上各类控件的UINavigationBar。首先,我们先来看一看官方文档怎么介绍UINavigationBar,A UINavigationBar object is a bar, typically displayed at the top of the window, containing buttons for navigating within a hierarchy of screens. The primary components are a left (back) button, a center title, and an optional right button. You can use a navigation bar as a standalone object or in conjunction with a navigation controller object.组成如下图左边所示。最重要的一部分我用蓝色加粗标出来了,就是说这个UINavigationBar主要是由左右按钮控件、中间标题控件组成。原生的导航条上的返回(back)按钮,一般是显示一个返回箭头+上一页面的标题(或者是 返回箭头+Back),如下图右边所示。

2.1 导航条上的按钮三兄弟

  在前面我们也提到了,在导航栏上有左右按钮和返回按钮,官方称谓是backBarButtonItem、leftBarButtonItem、rightBarButtonItem。他们都属于UINavigationItem的组成部分,都显示在navigationBar上,都属于UIBarButtonItem类,所以我给他们取名为导航条上的按钮三兄弟,哈哈哈。。。

  首先,我们来说一下leftBarButtonItem、rightBarButtonItem,这两个是孪生兄弟,唯一的区别就是在导航条上的位置,顾名思义,leftBarButtonItem在导航条左侧,rightBarButtonItem在导航条右侧。此外,还有一点需要我们注意的是navigationBar上的leftBarButtonItem、rightBarButtonItem可以有多个,用法也非常简单,常见用法就是一般在 - (void)viewDidLoad  中添加按钮,然后添加按钮的点击功能即可。

//添加取消btn
UIBarButtonItem *cancelBtn = [[UIBarButtonItem alloc] initWithTitle:@"取消" style:UIBarButtonItemStylePlain target:self action:@selector(navBtnPress:)] ;
cancelBtn.tag = 1000 ;
self.navigationItem.leftBarButtonItem = cancelBtn ;
//添加完成btn
UIBarButtonItem *createBtn = [[UIBarButtonItem alloc] initWithTitle:@"完成" style:UIBarButtonItemStylePlain target:self action:@selector(navBtnPress:)] ;
createBtn.enabled = NO ;        //刚开始设置为不可选
createBtn.tag = 1001 ;
self.navigationItem.rightBarButtonItem = createBtn ;
/**
 导航栏 取消 完成 按钮的操作
 @param sender <#sender description#>
 */
- (void) navBtnPress:(UIButton *)sender{
    if (sender.tag == 1000) {
        //取消
         NSLog(@"cancel"); 
    } else {
        //完成
        NSLog(@"create");
    }
}

2.2 导航条上的backBarButtonItem

  同样的,我们首先从官方文档了解一下backBarButtonItem的描述: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.说明了backBarButtonItem只能自定义image和title,不能重写target or action,系统会忽略其他的相关设置项。

  Note: If the title of your back button is too long to fit in the available space on the navigation bar, the navigation bar may substitute the string “Back” in place of the button’s original title. The navigation bar does this only if the back button is provided by the previous view controller. If the new top-level view controller has a custom left bar button item—an object in the leftBarButtonItems or leftBarButtonItem property of its navigation item—the navigation bar does not change the button title.这段描述了关于backBarButtonItem的一些特殊点,如果你上一级设置的backBarButtonItem的标题过长(没有设置则默认是上一级标题),那么系统可能会自动用“Back/返回”来代替返回按钮中的标题。此外,如果是自定义的左按钮,则系统不会修改其值。

2.3 backBarButtonItem和leftBarButtonItem的区别

  • backBarButtonItem和另外两兄弟是有区别的,比如当前有AController准备push到BController,设置backBarButtonItem的title和image需要在AController内设置,在调用AController Push:B之前进行设置, AController.navigationItem.backBarButtonItem = ....  ,其他两兄弟则是在BController的ViewDidload后设置均可。所以,如果我们一定需要重写返回键的action做一些其他的工作,则需要自定义一leftBarButtonItem,因为系统定义leftBarButtonItem的显示优先级比backBarButtonItem优先级高,当存在leftBarButtonItem时,自动忽略backBarButtonItem,达到重写backBarButtonItem的目的。
  • backBarButtonItem的自定义不会影响系统的侧滑返回手势,而leftBarButtonItem的自定义则会禁用侧滑返回手势。
    UIButton *backButton = [UIButton buttonWithType:UIButtonTypeCustom]; 
    //对按钮的个性化设定
    UIBarButtonItem *barItem = [[UIBarButtonItem alloc] initWithCustomView:backButton]; 
    self.navigationItem.leftBarButtonItem = barItem; //侧滑手势失效
    self.navigationItem.backBarButtonItem = barItem; //不影响侧滑手势
  • backBarButtonItem的自定义不能影响返回按钮的标题和图片,不会隐藏最左边的返回箭头backIndicatorImage,而leftBarButtonItem的自定义则会使最左边的返回箭头消失backIndicatorImage。

2.4 各个对象下的backBarButtonItem的区别 

  这一部分参考自:http://blog.csdn.net/dreamno23/article/details/21085783,还有些没弄明白,后面有时间再理一理这一块。

  对于导航栏上的按钮三兄弟,我们在3个类下面都能发现他们,比如当前在一个UIViewController内,输入以下方法都能发现他们。(同leftBarButtonItem | rightBarButtonItem)

self.navigationItem.backBarButtonItem

self.navigationController.navigationItem.backBarButtonItem

self.navigationController.navigationBar.backItem.backBarButtonItem

比如在AController->BController,在A设置了self.navigationItem.backBarButtonItem,经过试验发现,这个backBarButtonItem为BController的self.navigationController.navigationBar.backItem.backBarButtonItem。

UIViewController的属性navigationItem正是被当前UINavigationBar--[UINavigationBar appearance]管理的属性

//The navigation item used to represent the view controller in a parent’s navigation bar. (read-only)
@property(nonatomic, readonly, retain) UINavigationItem *navigationItem;

self.navigationController.navigationItem.backBarButtonItem则是表示当前navigationController的parent的UINavigationBar,一般情况下没有这样的嵌套。

 

posted on 2018-01-04 23:11  mukekeheart  阅读(10093)  评论(0编辑  收藏  举报