搞清leftBarButtonItem、backBarButtonItem
我们在A视图设置的代码,系统是如何实现从A视图push到B视图后,显示在B视图的?
push说明图.png
显示原理:
1、如果B视图有一个自定义的左侧按钮(leftBarButtonItem),则会显示这个自定义按钮;
2、如果B没有自定义按钮,但是A视图的backBarButtonItem属性有自定义项,则显示这个自定义项;
3、如果前2条都没有,则默认显示一个后退按钮,后退按钮的标题是A视图的标题。
leftBarButtonItem会屏蔽掉系统的侧滑返回手势,而使用backBarButtonItem会保留侧滑手势。
可以在baseViewController里把leftBarButtonItem和backBarButtonItem的创建和事件都写好,需要的时候就调用。如果需要拦截返回事件,就使用leftBarButtonItem,缺点是失去了侧滑返回效果。即使打开侧滑手势,也拦截不了返回方法。
1. backBarButtonItem
要 返回 文字
特点:
1.使用了自定义的返回箭头图片
2.有返回文字
3.保存了iOS 7以上系统的pop手势返回。
- (void)setCustomBackBarItem{
//替换系统的“<"图标
[self.navigationController.navigationBar setBackIndicatorImage:[UIImage imageNamed:@"fan_hui.png"]];
[self.navigationController.navigationBar setBackIndicatorTransitionMaskImage:[UIImage imageNamed:@"fan_hui.png"]];
UIButton *btn = [[UIButton alloc]init];
btn.frame =CGRectMake(0, 0, 57, 44);//frame 不显示
[btn setImage:[UIImage imageNamed:@"fan_hui.png"] forState:(UIControlStateNormal)];//不起作用
[btn addTarget:self action:@selector(backBarButtonItemReturnAction) forControlEvents:(UIControlEventTouchUpInside)];//不起作用
UIBarButtonItem *leftItem0 = [[UIBarButtonItem alloc]initWithCustomView:btn];
self.navigationItem.backBarButtonItem = leftItem0;
self.navigationItem.backBarButtonItem.title = @"返回";
//设置leftItem0之后,<之后显示的不是上一级的title,而是返回
//效果等同于
//UIBarButtonItem *leftItem = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStyleBordered target:nil action:nil];
//self.navigationItem.backBarButtonItem = leftItem;
self.navigationController.navigationBar.tintColor = TITLE;
}
- (void)backBarButtonItemReturnAction{
[self.navigationController popViewControllerAnimated:YES];
}
不要 返回 文字
特点:
1.使用了自定义的返回箭头图片
2.没有返回文字
3.保存了iOS 7以上系统的pop手势返回。
[self.navigationController.navigationBar setBackIndicatorImage:[UIImage imageNamed:@"navibar_back_btn_bg_normal.png"]];
[self.navigationController.navigationBar setBackIndicatorTransitionMaskImage:[UIImage imageNamed:@"navibar_back_btn_bg_normal.png"]];
UIBarButtonItem *backItem = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStyleBordered target:nil action:nil];
self.navigationItem.backBarButtonItem = backItem;
设置自定义的返回箭头
根据文档:
1、只有这两个属性都设置了,才会显示自定义的图片。
2、这两个属性,只能用于iOS 7以上。
3、很多人问为什么要设置“backIndicatorTransitionMaskImage”这个属性,看官方文档就知道,当push、pop时显示的是这张image,但是疑问的是,当我设置这张照片跟“backIndicatorImage”属性的照片不一样时,系统不管是push,还是pop,还是完成时,都只会显示"backIndicatorImage"属性设置的照片。
2. leftBarButtonItem
一、自定义leftBarButtonItem
第一种,自定义导航栏的返回按钮,虽然这看起来是一种方式,但是也不能从根本上解决;比如整个应用的返回键都是统一的,这时候再重写了某个界面的返回按钮感觉就不统一了。而且每有一个界面有这个需求都需要重新自定义一个返回按钮,显得不优雅。
backBarButtonItem不能用View视图方法自定义,而leftBarButtonItem可以哦!
加上 self.navigationController.interactivePopGestureRecognizer.delegate = self;
才有手势返回哦!
方案一:在B中开启侧滑手势
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
if([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
self.navigationController.interactivePopGestureRecognizer.delegate = (id)self;
}
}
- (void)setCustomLeftButtonItem{
UIButton *btn = [UIButton buttonWithType:(UIButtonTypeCustom)];
btn.frame =CGRectMake(0, 0, 57, 44);
[btn setTitle:@"返回" forState:(UIControlStateNormal)];
[btn setTitleColor:TITLE forState:UIControlStateNormal];
[btn setImage:[UIImage imageNamed:@"fan_hui.png"] forState:(UIControlStateNormal)];
[btn addTarget:self action:@selector(leftBarButtonItemReturnAction) forControlEvents:(UIControlEventTouchUpInside)];
UIBarButtonItem *leftItem0 = [[UIBarButtonItem alloc]initWithCustomView:btn];
self.navigationItem.leftBarButtonItem = leftItem0;
self.navigationController.navigationBar.tintColor = TITLE;
}
- (void)leftBarButtonItemReturnAction{
[self.navigationController popViewControllerAnimated:YES];
}
方案二:在A中设置
NextViewController *GoodsDetailVC = [[UIStoryboard storyboardWithName:@"Main" bundle:nil]instantiateViewControllerWithIdentifier:@"NextViewController"];
//开启iOS7的滑动返回效果 能实现手势返回,但是监测不到点击返回事件
if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
self.navigationController.interactivePopGestureRecognizer.delegate = nil;
}
[self.navigationController pushViewController:GoodsDetailVC animated:YES];
二、为 UINavigationController 添加 category
此方法来自 github:UIViewController-BackButtonHandler
由于系统的 UINavigationController
使用了一个 UINavigationBar
来管理 Controller 的 pop 和 push 等操作,所以仔细查看 UINavigationBar
的 API,会发现一个 UINavigationBarDelegate
,它包含了四个方法:
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPushItem:(UINavigationItem *)item; // called to push. return NO not to.
- (void)navigationBar:(UINavigationBar *)navigationBar didPushItem:(UINavigationItem *)item; // called at end of animation of push or immediately if not animated
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item; // same as push methods
- (void)navigationBar:(UINavigationBar *)navigationBar didPopItem:(UINavigationItem *)item;
我们会惊喜的第一个不就是我们想要的效果吗?因为该方法返回 YES 则 pop,若返回NO,则不POP。
因此我们可以为 UINavigatonController 创建一个 Category,来定制navigationBar: shouldPopItem:
的逻辑。这里需要注意的是,我们不需要去设置 delegate,因为 UINavigatonController 自带的 UINavigationBar 的 delegate 就是导航栏本身。这样还有个问题就是,那在实际的 Controller 里面怎么控制呢?因此同样需要对 UIViewController 添加一个 Protocol,这样在 Controller 中使用该 Protocol 提供的方法即可进行控制了,代码如下:
// UIViewController+BackButtonHandler.h
@protocol BackButtonHandlerProtocol <NSObject>
@optional
// 重写下面的方法以拦截导航栏返回按钮点击事件,返回 YES 则 pop,NO 则不 pop
-(BOOL)navigationShouldPopOnBackButton;
@end
@interface UIViewController (BackButtonHandler) <BackButtonHandlerProtocol>
@end
// UIViewController+BackButtonHandler.m
@implementation UIViewController (BackButtonHandler)
@end
@implementation UINavigationController (ShouldPopOnBackButton)
- (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(navigationShouldPopOnBackButton)]) {
shouldPop = [vc navigationShouldPopOnBackButton];
}
if(shouldPop) {
dispatch_async(dispatch_get_main_queue(), ^{
[self popViewControllerAnimated:YES];
});
} else {
// 取消 pop 后,复原返回按钮的状态
for(UIView *subview in [navigationBar subviews]) {
if(0. < subview.alpha && subview.alpha < 1.) {
[UIView animateWithDuration:.25 animations:^{
subview.alpha = 1.;
}];
}
}
}
return NO;
}
到这儿,几乎就完美的解决一开始的问题了,使用方法也非常简单
(1). 在一个 Controller 中:
#import "UIViewController+BackButtonHandler.h"
(2). 重写 navigationShouldPopOnBackButton 方法
- (BOOL)navigationShouldPopOnBackButton{
[[[UIAlertView alloc] initWithTitle:@"提示" message:@"确定返回上一界面?"
delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"确定", nil] show];
return NO;
}
// UIAlertViewDelegate
- (void)alertView:(UIAlertView *)alertView willDismissWithButtonIndex:(NSInteger)buttonIndex{
if (buttonIndex==1) {
[self.navigationController popViewControllerAnimated:YES];
}
}
总结:
这两种方法点击返回的时候,都会触发拦截事件。但是手势返回,是不会触发的。手势返回的时候,监测不到leftBarButtonItemReturnAction和navigationShouldPopOnBackButton方法。
3.侧滑手势
以上的方法,都是基于系统自带的UINavigationController来自定义
//官方文档:
NS_CLASS_AVAILABLE_IOS(2_0) @interface UINavigationController : UIViewController
@property(nullable, nonatomic, weak) id<UINavigationControllerDelegate> delegate;
@property(nullable, nonatomic, readonly) UIGestureRecognizer *interactivePopGestureRecognizer NS_AVAILABLE_IOS(7_0) __TVOS_PROHIBITED;
网友评论