iOS导航栏控制的一些总结

前言

许久不写UI,对UI的很多东西都生疏了,最近使用导航栏的各种场景做一些总结。

1.导航栏的显示与隐藏

导航栏的显示与隐藏,分两种情况:

1.从不显示导航栏的页面push到显示导航栏的页面。

2.从显示导航栏的页面Push到不显示导航栏的页面。

注意:

1.如果导航栏不显示时,系统的侧滑返回功能无效。

2.虽然侧滑返回功能无效,但是导航栏的 .interactivePopGestureRecognizer.delegate还是存在的。

针对以上两种情况分别处理,整个Push过程都假设是从A页面跳转到B页面

1.1 从不显示导航栏的页面Push到显示导航栏的页面。

关于导航栏的显示,是否顺滑,是通过如下两个方法来控制。

// 不显示动画,导航栏显示就比较突兀
[self.navigationController setNavigationBarHidden:YES];

// 显示动画,在侧滑时,导航栏显示就比较顺滑
[self.navigationController setNavigationBarHidden:YES animated:YES];

所以,做法是:

A页面:

- (void)viewWillAppear:(BOOL)animated
{
 [super viewWillAppear:animated];

 [self.navigationController setNavigationBarHidden:YES animated:YES];
}

B页面:

- (void)viewWillAppear:(BOOL)animated
{
 [super viewWillAppear:animated];

 [self.navigationController setNavigationBarHidden:NO animated:YES];
}

1.2 从显示导航栏的页面跳转到不显示导航栏的页面

这种情况的做法如下:

A页面:

- (void)viewWillAppear:(BOOL)animated
{
 [super viewWillAppear:animated];
 [self.navigationController setNavigationBarHidden:NO animated:YES];
}

B页面:

// 在页面将要出现时,记录原始侧滑手势代理对象,并将手势代理设置为当前页面
- (void)viewWillAppear:(BOOL)animated
{
 [super viewWillAppear:animated];

 self.interactivePopDelegate = self.navigationController.interactivePopGestureRecognizer.delegate;
 self.navigationController.interactivePopGestureRecognizer.delegate = self;

 [self.navigationController setNavigationBarHidden:YES animated:YES];
}

// 在页面消失时,还原侧滑手势代理对象
- (void)viewDidDisappear:(BOOL)animated
{
 [super viewDidDisappear:animated];

 self.navigationController.interactivePopGestureRecognizer.delegate = self.interactivePopDelegate;
 self.interactivePopDelegate = nil;
}

// 实现手势代理,为了防止影响其他手势,可以判断一下手势类型
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
 if ([gestureRecognizer isKindOfClass:[UIScreenEdgePanGestureRecognizer class]]) {
 return YES;
 }
 ...... 其他手势的处理

 return NO;
}

2.统一重写导航栏返回按钮

有时候,我们可能需要统一工程中的返回按钮样式,比如都是 箭头+返回 或者都是 箭头。

方案有两种:

1.创建一个BaseViewController,然后统一设置navigationItem.leftBarButtonItem

2.重写导航控制器的Push方法,在push之前,设置navigationItem.backBarButtonItem

注意:

如果重写了导航栏的leftBarButtonItem,那么侧滑返回功能也就失效了,需要侧滑返回功能需要自己处理。

第一种方案比较简单就不做赘述了,第二种方案是这样的:

自定义导航控制器,然后重写如下方法:

- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
 UIBarButtonItem *backItem = [[UIBarButtonItem alloc] initWithTitle:@"返回" style:UIBarButtonItemStyleDone target:nil action:nil];
 viewController.navigationItem.backBarButtonItem = backItem;

 [super pushViewController:viewController animated:animated];
}

如果不需要返回这两个字,只需要这样写就好。

- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
 UIBarButtonItem *backItem = [[UIBarButtonItem alloc] initWithTitle:nil style:UIBarButtonItemStyleDone target:nil action:nil];
 viewController.navigationItem.backBarButtonItem = backItem;

 [super pushViewController:viewController animated:animated];
}

3.监听返回按钮的点击事件

在有些场景,我们需要监听返回按钮的事件。比如,当页面用户输入了一些内容后,用户要点击返回,想要回到上一个页面时,提醒用户是否要缓存已经输入的内容。

如果我们重写了导航栏的返回按钮,那么处理这种情况就很Easy,不做赘述了。

但是,如果我们没有重写过系统的返回按钮,想要处理这种情况就比较麻烦,但是也是可以处理的。

处理步骤如下:

1.首先创建一个UIViewController的类别,头文件(.h)的内容如下:

@protocol BackItemProtocol <NSObject>

- (BOOL)navigationShouldPopWhenBackButtonClick;

@end

@interface UIViewController (BackItem)<BackItemProtocol>

@end

@interface UINavigationController (BackItem)

@end

包含一个协议、UIViewController的类别、UINavigationController的类别。

然后,实现文件(.m)如下:

#import "UIViewController+BackItem.h"

@implementation UIViewController (BackItem)

- (BOOL)navigationShouldPopWhenBackButtonClick
{
 return YES;
}

@end

@implementation UINavigationController (BackItem)

// 这个其实是导航栏的协议方法,在这里重写了
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
{
 if([self.viewControllers count] < [navigationBar.items count]) {
 return YES;
 }

 BOOL shouldPop = YES;
 UIViewController *vc = [self topViewController];
 if([vc respondsToSelector:@selector(navigationShouldPopWhenBackButtonClick)]) {
 shouldPop = [vc navigationShouldPopWhenBackButtonClick];
 }

 if (shouldPop) {
 dispatch_async(dispatch_get_main_queue(), ^{
 [self popViewControllerAnimated:YES];
 });
 } else {
 for(UIView *subview in [navigationBar subviews]) {
 if(subview.alpha < 1) {
 [UIView animateWithDuration:.25 animations:^{
  subview.alpha = 1;
 }];
 }
 }
 }
 return NO;
}

@end

默认是,不需要处理返回按钮的事件,直接使用系统的pop方法。

但是,如果我们需要在用户点击返回按钮时,弹窗提示,那就需要导入这个类别。

然后,重写一个方法:

- (BOOL)navigationShouldPopWhenBackButtonClick
{
 BOOL isFlag = 输入框不为空等等条件
 if (isFlag) {
 UIAlertController *alertVC = [UIAlertController alertControllerWithTitle:nil message:@"是否保存修改" preferredStyle:UIAlertControllerStyleAlert];
 UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
 // 这里延时执行是因为UIAlertController阻塞UI,可能会导致动画的不流畅
 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
 [self.navigationController popViewControllerAnimated:YES];
 });
 }];
 UIAlertAction *saveAction = [UIAlertAction actionWithTitle:@"保存" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
 // 这里延时执行是因为UIAlertController阻塞UI,可能会导致动画的不流畅
 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
 [self rightClick];
 });
 }];
 [alertVC addAction:cancelAction];
 [alertVC addAction:saveAction];
 [self presentViewController:alertVC animated:YES completion:nil];
 return NO;
 }

 return YES;
}

4.导航控制器的页面跳转方式

安卓中的页面跳转有四种方式: standard、singleTop、singleTask、singleInstance。

例如singleTask,在做IM类App,跳转到聊天室的场景,就非常有用,可以保证控制器栈中只有一个聊天室,避免返回时层级太深。

iOS端如果要仿这个效果的话,可以利用导航控制器的API:

- (void)setViewControllers:(NSArray<UIViewController *> *)viewControllers animated:(BOOL)animated

首先,为UINavigationController 创建一个类别。

比如:

UINavigationController+HLPushAndPop.h
UINavigationController+HLPushAndPop.m

然后,新增几个方法:

拿两个方法来举例

- (void)hl_pushSingleViewController:(UIViewController *)viewController
  animated:(BOOL)animated;

- (void)hl_pushSingleViewController:(UIViewController *)viewController
  parentClass:(Class)parentClass
  animated:(BOOL)animated;

再然后,实现方法:

实现步骤:

  1. 创建新的数组复制导航控制器原来的堆栈中的控制器。
  2. 在原始堆栈数组中判断是否存在该类型的控制器,如果存在记录其索引。
  3. 在复制的数组中将索引及上方所有控制器移除。
  4. 把将要push出来的控制器添加到复制的数组中。
  5. 将新的控制器数组设置为导航控制器的栈数组,根据参数判断是否要显示动画。

我这边做了一些发散,因为一些类可能会有很多子类,那么想要保证父类以及子类的实例都只有一个,所以将方法做了改进。

- (void)hl_pushSingleViewController:(UIViewController *)viewController
  animated:(BOOL)animated
{
 [self hl_pushSingleViewController:viewController parentClass:viewController.class animated:animated];
}

- (void)hl_pushSingleViewController:(UIViewController *)viewController
  parentClass:(Class)parentClass
  animated:(BOOL)animated
{
 if (!viewController) {
 return;
 }
 // 如果要push的界面不是 parentClass以及其子类的实例,则按照方法1处理
 if (![viewController isKindOfClass:parentClass]) {
 [self hl_pushSingleViewController:viewController animated:animated];
 return;
 }

 // 判断 导航控制器堆栈中是否有parentClass以及其子类的实例
 NSArray *childViewControllers = self.childViewControllers;
 NSMutableArray *newChildVCs = [[NSMutableArray alloc] initWithArray:childViewControllers];
 BOOL isExit = NO;
 NSInteger index = 0;
 for (int i = 0; i < childViewControllers.count; i++) {
 UIViewController *vc = childViewControllers[i];
 if ([vc isKindOfClass:parentClass]) {
 isExit = YES;
 index = i;
 break;
 }
 }

 // 如果不存在,则直接push
 if (!isExit) {
 [self pushViewController:viewController animated:animated];
 return;
 }

 // 如果存在,则将该实例及上面的所有界面全部弹出栈,然后将要push的界面放到栈顶。
 for (NSInteger i = childViewControllers.count - 1; i >= index; i--) {
 [newChildVCs removeObjectAtIndex:i];
 }

 [newChildVCs addObject:viewController];
 viewController.hidesBottomBarWhenPushed = (newChildVCs.count > 1);
 [self setViewControllers:newChildVCs animated:animated];
}

当然了,除了上面这些场景,还可以扩展出一些其他的场景,比如我们期望将要push出来的控制器再某个栈中控制器的后面或者前面,这样当点击返回或者侧滑时,就直接回到了指定页面了。

或者我们知道将要返回的页面的类型,直接pop回指定页面。

扩展出来的其他方法都在Demo中了,有兴趣的可以看一下。

地址是:HLProject (本地下载)

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对我们的支持。

(0)

相关推荐

  • iOS如何去掉导航栏(UINavigationBar)下方的横线

    网上有很多关于隐藏的方法,设置后能够成功,但是跳转到其他界面的时候发现,其他界面横线也被隐藏了. 目前主流的方法是将shadowImage用一张空的图片图片替换掉 可是这种方法不能解决navigationController里面的某个界面隐藏,其他不变的问题. [self.navigationController.navigationBar setBackgroundImage:[[UIImage alloc] init] forBarMetrics:UIBarMetricsDefault];

  • 详解iOS11关于导航栏问题

    前言 iOS11导航栏除了新加入了largeTitles和searchController两个新特性,可能是加入largeTitles的原因其结构较iOS 10发生了些变化. iOS11之前导航栏的navigationBarButton则直接添加在navigationBar上面 在iOS11之后,苹果添加了新的类来管理,可以看到titleView直接加在_UINavigationBarContentView上,UIBarButtonItem则添加在_UIButtonBarStackView上面,

  • iOS实现顶部标签式导航栏及下拉分类菜单

    本文实例为大家分享了iOS实现顶部标签式导航栏及下拉分类菜单的全部过程,供大家参考,具体内容如下 当内容及分类较多时,往往采用顶部标签式导航栏,例如网易新闻客户端的顶部分类导航,最近刚好有这样的应用场景,参考网络上一些demo,实现了这种导航效果,记录一些要点. 效果图(由于视频转GIF掉帧,滑动和下拉动画显得比较生硬,刚发现quickTime可以直接录制手机视频,推荐一下,很方便) 1.顶部标签式导航栏 (1)实现思路 其实就是在上下两个UIScrollView上做文章,实现联动选择切换的效果

  • iOS定制UISearchBar导航栏同步iOS11的方法

    系统原生的UISearchBar在iOS 11经历了一次变革,高度由原来的44变成了56 (使用默认高度的估计都被坑了),样式也发生了些微的变化,比如在未输入状态下圆角变化,放大镜图标和文本的文字不再居中而是靠左了.具体看图 一些主流App也常见在导航栏嵌入searchBar,以网易云音乐和知乎为例,左边是主页,右边是搜索页面 (注意光标). 实现思路与案例 核心思想是设置导航栏的titleView和左右的barButtonItem.主要有3种方式 首页导航栏的titleView使用button

  • 关于iOS导航栏返回按钮问题的解决方法

    最近遇到一个关于导航栏返回按钮的问题,因为之前项目里面都是用的系统默认的返回按钮样式所以没有想过要去更改,后来有需要将返回按钮箭头旁边的文字去掉,同时将该返回按钮的点击事件重新定义.一开始尝试自定义按钮然后设置为leftBarButtonItem,但是这样图片可能跟系统自带的不一样,还有就是返回按钮的位置跟系统自带的不一样.后来找了一些资料,发现将文字去掉比较简单,一般做法是控制器中添加如下代码,然后他的下一级控制就有一个只有箭头没有文字返回按钮: 复制代码 代码如下: UIBarButtonI

  • iOS应用开发中导航栏按钮UIBarButtonItem的添加教程

    1.UINavigationController导航控制器如何使用 UINavigationController可以翻译为导航控制器,在iOS里经常用到. 我们看看它的如何使用: 下面的图显示了导航控制器的流程.最左侧是根视图,当用户点击其中的General项时 ,General视图会滑入屏幕:当用户继续点击Auto-Lock项时,Auto-Lock视图将滑入屏幕.相应地,在对象管理上,导航控制器使用了导航堆栈.根视图控制器在堆栈最底层,接下来入栈的是General视图控制器和Auto-Lock

  • IOS仿今日头条滑动导航栏

    之前在我们平台给大家分享了网易首页导航封装类.网易首页导航封装类优化,今天在前两个的基础上仿下今日头条. 1.网易首页导航封装类中主要解决了上面导航的ScrollView和下面的页面的ScrollView联动的问题,以及上面导航栏的便宜量. 2.网易首页导航封装类优化中主要解决iOS7以上滑动返回功能中UIScreenEdgePanGestureRecognizer与ScrollView的滑动的手势冲突问题. 今天仿今日头条滑动导航和网易首页导航封装类优化相似,这个也是解决手势冲突,UIPanG

  • iOS 封装导航栏及返回,获取控件所在控制器的实例

    当一个项目发现每个返回的按钮都是一样的,并且标题的字体也不是系统的字体,如果每个页面都去设置返回按钮,重新设置标题字体,这样代码看着繁杂,而且会浪费很多时间,这时候就有必要封装一下了... 首先返回按钮,需要在当前页面pop 到上一个页面的话,有两种方式:一 写一个点击代理,在用到的页面实现它,二 就是获取button所在的当前控制器,然后pop出去. 但是第一个方法,还需要到用到的页面去实现代理,也比较麻烦,那就来说第二种 首先获取当前控制器的方法: UINavigationControlle

  • iOS界面跳转时导航栏和tabBar的隐藏与显示功能

    一.当A页面要push到B页面,需要将B页面的导航栏隐藏时,我们只需要在A页面中重写以下两个方法: override func viewWillAppear(animated: Bool) { super.viewWillAppear(animated) self.navigationController?.setNavigationBarHidden(true, animated: true) } override func viewWillDisappear(animated: Bool)

  • 简单好用的iOS导航栏封装.runtime属性控制实例代码

    前言 本文主要给大家介绍一个不错的导航栏控制工具,可以大大的简化代码,并保留系统特性,不用自定义导航栏,不用继承base. 下面话不多说了,来一起看看详细的介绍吧 UIViewController+YINNav 效果 @interface UIViewController (YINNav) /** 控制屏幕方向 在appdelegate 实现 - (UIInterfaceOrientationMask)application:(UIApplication *)application suppor

随机推荐