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;
再然后,实现方法:
实现步骤:
- 创建新的数组复制导航控制器原来的堆栈中的控制器。
- 在原始堆栈数组中判断是否存在该类型的控制器,如果存在记录其索引。
- 在复制的数组中将索引及上方所有控制器移除。
- 把将要push出来的控制器添加到复制的数组中。
- 将新的控制器数组设置为导航控制器的栈数组,根据参数判断是否要显示动画。
我这边做了一些发散,因为一些类可能会有很多子类,那么想要保证父类以及子类的实例都只有一个,所以将方法做了改进。
- (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中了,有兴趣的可以看一下。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对的支持。