现象
在使用iOS导航栏时踩上的一些坑,假设两个UIViewController:A、B,B是A的子级视图控制器
- 在B上自定义返回键backBarButtonItem无效及界面异常
- 修改所有UIViewController导航栏的按钮
原因及解决方法
- 问题一:在B上自定义返回键backBarButtonItem无效及界面异常,可能原因解决方法如下:
- 修改返回键标题无效,在A中设置了UIViewController的navigationItem.title,如下图,不设置时,该值默认是nil;如果该A先入栈,而后B入栈,此时栈顶的B的返回键自动被设置为A的navigationItem.title的值,如果A的title为nil,则B的返回键会使用“Back”;
- 重新定义返回键无效,如果要修改B中的backBarButtonItem,可以创建一个新的UIBarButtonItem来替换,直接修改backBarButtonItem的title是无效的
推荐使用- (instancetype)initWithCustomView:(UIView *)customView;来自定义不同的样子;但是如果只是想去掉backBarButtonItem,则可以使用一个空的UIBarButtonItem来替换
self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStyleDone target:nil action:nil];
补充:如果不修改内容,就是装饰一下文本,可以使用setTitleTextAttributes:forState:
- 重定义的返回键标题重叠,涉及到leftBarButtonItem,这个的使用场景有点像淘宝和微信的文章,使用在导航栏左侧需要多个按钮时,比如‘Back’、‘Close’多个并列存在的情况,也可以用leftBarButtonItem来设置为一个返回键,但是需要设置backBarButtonItem为空或使用hidesBackButton隐藏 (2017.09.04修改),不然会出现两个标题重叠的情况,leftBarButtonItem的设置方法参考backBarButtonItem的设置方法,还可以批量设置多个,使用leftBarButtonItems。
- 问题二:修改所有UIViewController导航栏的按钮
实际运用中,像导航栏这样几乎所有UIViewController都会用到的东西基本应该在项目开始就准备后,如果要修改属性,秉持优化代码的精神,也应该在一处修改,然后在所有其他地方都生效,因此,这里有两种方法:
- 继承UIViewController重写导航栏,继承UIViewController,自定义一个导航栏,隐藏系统的导航栏,然后在需要导航栏的地方用一句代码实现创建导航栏。
如我使用过的一个:QHBasicViewController(来源已不可考)
QHBasicViewController.h
#import
@interface QHBasicViewController : UIViewController
@property (nonatomic, strong) UILabel* navTitleLabel;///导航栏标题
@property (nonatomic, strong, readonly) UIView *navView;///导航栏,只读
@property (nonatomic, strong) UIImageView* navBackgroundView;///导航栏背景
@property (nonatomic, strong, readonly) UIView* leftV;///导航栏左侧View,只读
@property (nonatomic, strong, readonly) UIView *rightV;///导航栏右侧View,只读
/** 创建导航栏
* @param szTitle 导航栏标题
* @param menuItem block回调,需要在这里定义导航栏上的各个按钮
*/
- (void)createNavWithTitle:(NSString *)szTitle createMenuItem:(UIView *(^)(int nIndex))menuItem;
@end
QHBasicViewController.m
#import "QHBasicViewController.h"
#import "HHUtil.h"
#define StatusbarHeight ((isIos7 >= 7 && __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_6_1)?20.f:0.f)
#pragma mark - QHBasicViewController
@interface QHBasicViewController ()
{
}
@end
@implementation QHBasicViewController
- (void)viewWillAppear:(BOOL)animated
{
//隐藏UIViewController的导航栏
[[self navigationController] setNavigationBarHidden:YES];
[super viewWillAppear:TRUE];
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
}
- (void)createNavWithTitle:(NSString *)szTitle createMenuItem:(UIView *(^)(int nIndex))menuItem
{
const CGFloat screenWidth = [[UIScreen mainScreen] bounds].size.width;
const CGFloat navigationViewHeigth = 44.f;
const CGFloat _nSpaceNavY = (isIos7 >= 7 && __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_6_1) ? 0 : 20;
UIImageView *navIV = [[UIImageView alloc] initWithFrame:CGRectMake(0, _nSpaceNavY, screenWidth, 64 - _nSpaceNavY)];
[navIV setImage:[UIImage imageNamed:@"bg_nav.png"]];
_navBackgroundView = navIV;
[self.view addSubview:navIV];
/* { 导航条 } */
_navView = [[UIImageView alloc] initWithFrame:CGRectMake(0.f, StatusbarHeight, screenWidth, navigationViewHeigth)];
((UIImageView *)_navView).backgroundColor = [UIColor clearColor];
[self.view addSubview:_navView];
_navView.userInteractionEnabled = YES;
//
if (szTitle != nil)
{
UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectMake((_navView.bounds.size.width - 240)/2, (_navView.bounds.size.height - 40)/2, 240, 40)];
[titleLabel setText:szTitle];
[titleLabel setTextAlignment:NSTextAlignmentCenter];
[titleLabel setTextColor:[UIColor whiteColor]];
[titleLabel setFont:[UIFont boldSystemFontOfSize:18]];
[titleLabel setBackgroundColor:[UIColor clearColor]];
titleLabel.numberOfLines = 2;
_navTitleLabel = titleLabel;
[_navView addSubview:_navTitleLabel];
}
//
UIView *item1 = menuItem(0);
if (item1 != nil)
{
_leftV = item1;
[_navView addSubview:item1];
}
UIView *item2 = menuItem(1);
if (item2 != nil)
{
_rightV = item2;
[_navView addSubview:item2];
}
}
@end
在创建你自己的UIViewController的时候,需要做的就是继承QHBasicViewController而不是UIViewController,然后在viewDidLoad的时候createNavWithTitle: createMenuItem:创建导航栏,然后在块回调中定义你自己的按钮。
- 继承UINavigationController重写push方法,继承UINavigationController方法,重写push方法,在push前后统一修改backBarButtonItem、leftBarButtonItem和rightBarButtonItem,甚至其它的导航栏设置,比如自定义titleView,导航栏背景之类的。
奉上个人做的一个例子:
HHNavigationController.h
#import
@interface HHNavigationController : UINavigationController
/** 重载的initWithRootViewController:方法,附带初始化导航栏
* @param rootViewController 视图控制器
*/
- (instancetype)initWithRootViewController:(UIViewController *)rootViewController;
@end
HHNavigationController.m
#import "HHNavigationController.h"
#import
@interface HHNavigationController ()
@property (nonatomic, strong) UIBarButtonItem *backBarButtonItm;
@end
@implementation HHNavigationController
- (instancetype)initWithRootViewController:(UIViewController *)rootViewController
{
self = [super initWithRootViewController:rootViewController];
if (self) {
//设置导航栏和状态栏字体颜色为白色,导航栏背景设置为绿色
rootViewController.navigationController.navigationBar.barTintColor = RGBA(129, 164, 128, 1);//导航栏为绿色
rootViewController.navigationController.navigationBar.barStyle = UIBarStyleBlack;//状态栏字体为白色
rootViewController.navigationController.navigationBar.translucent = NO;//导航栏不透明
//设置侧滑滑动返回
self.interactivePopGestureRecognizer.delegate = self;
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - action
-(void)onBack
{
[self popViewControllerAnimated:YES];
}
#pragma mark - back button
-(UIBarButtonItem *)backBarButtonItm
{
if (!_backBarButtonItm) {
UIButton *backBtn = [UIButton buttonWithType:UIButtonTypeCustom];
backBtn.frame = CGRectMake(0, 0, 30, 30);
[backBtn setImage:[UIImage imageNamed:@"btn_back"] forState:UIControlStateNormal];
[backBtn addTarget:self action:@selector(onBack) forControlEvents:UIControlEventTouchUpInside];
UIBarButtonItem *backBarItm = [[UIBarButtonItem alloc] initWithCustomView:backBtn];
_backBarButtonItm = backBarItm;
}
return _backBarButtonItm;
}
#pragma mark - Override
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated {
viewController.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:nil style:UIBarButtonItemStyleDone target:nil action:nil];//返回键置空
[super pushViewController:viewController animated:animated];
if (viewController.navigationItem.leftBarButtonItem == nil && [self.viewControllers count] > 1) {
//设置返回键
viewController.navigationItem.leftBarButtonItem = self.backBarButtonItm;
//设置导航栏和状态栏字体颜色为白色,导航栏背景设置为绿色
viewController.navigationController.navigationBar.barTintColor = RGBA(129, 164, 128, 1);//导航栏为绿色
viewController.navigationController.navigationBar.barStyle = UIBarStyleBlack;//状态栏字体为白色
viewController.navigationController.navigationBar.translucent = NO;//导航栏不透明
}
}
@end
缺点
- 原生的导航栏虽然方便,但是牺牲了很多便利,自定义起来比较麻烦,特别是如果要求动画的话(这个需求很正常),基本就用不了只能自己写一个伪导航栏来替代
- 不容易统一,如果使用present方法,呈现的方式都不同,也不会有导航栏,虽然有方法加导航栏,但是还是有差异,也容易给代码的编写造成困扰
- 难定制,遇到定制需求,由于backBarButtonItem本身要求的在加载到bar之前设置好的限制,很难动态的变化
修改
2017.09.02 16:13 V1.0 Create
2017.09.04 09:36 V1.1 添加backBarButtonItem的隐藏方法介绍