許久不寫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