许久不写UI,对UI的不少东西都生疏了,最近使用导航栏的各类场景作一些总结。git
导航栏的显示与隐藏,分两种状况:github
注意: 1.若是导航栏不显示时,系统的侧滑返回功能无效。 2.虽然侧滑返回功能无效,可是导航栏的
.interactivePopGestureRecognizer.delegate
仍是存在的。数组
针对以上两种状况分别处理,整个Push过程都假设是从A页面跳转到B页面缓存
关于导航栏的显示,是否顺滑,是经过以下两个方法来控制。bash
// 不显示动画,导航栏显示就比较突兀
[self.navigationController setNavigationBarHidden:YES];
// 显示动画,在侧滑时,导航栏显示就比较顺滑
[self.navigationController setNavigationBarHidden:YES animated:YES];
复制代码
因此,作法是: A页面:app
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.navigationController setNavigationBarHidden:YES animated:YES];
}
复制代码
B页面:async
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.navigationController setNavigationBarHidden:NO animated:YES];
}
复制代码
这种状况的作法以下: A页面:ide
- (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;
}
复制代码
有时候,咱们可能须要统一工程中的返回按钮样式,好比都是 箭头+返回
或者都是 箭头
。 方案有两种:ui
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];
}
复制代码
在有些场景,咱们须要监听返回按钮的事件。好比,当页面用户输入了一些内容后,用户要点击返回,想要回到上一个页面时,提醒用户是否要缓存已经输入的内容。
若是咱们重写了导航栏的返回按钮,那么处理这种状况就很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;
}
复制代码
安卓中的页面跳转有四种方式: 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;
复制代码
再而后,实现方法:
实现步骤:
我这边作了一些发散,由于一些类可能会有不少子类,那么想要保证父类以及子类的实例都只有一个,因此将方法作了改进。
- (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