iOS中定制导航栏背景
来源:互联网 发布:轻薄的游戏本知乎 编辑:程序博客网 时间:2024/05/21 09:38
一.iOS4中定制导航栏背景
在iOS4中通过重写UINavigationBar的drawRect:方法,可以修改导航栏的背景。
1.使用类别(Category)扩展重写drawRect:
@implementation UINavigationBar(CustomBackground)- (void)drawRect:(CGRect)rect { UIImage *image = [UIImage imageNamed:@"NavBar"]; [image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)]; return;}@end
通过类别重写后,其他代码不需做任何更改即可改变导航栏的背景。
2.通过创建UINavigationBar子类重写drawRect:
@implementation MyNavigationBar-(void)drawRect:(CGRect)rect{ [super drawRect:rect]; UIImage *image = [UIImage imageNamed:@"NavBar"]; [image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)];}@end
一般不建议对UINavigationBar进行子类化,创建UINavigationBar子类后,则相应UINavigationController都需要将设置为使用导航栏子类。
想定制导航栏吗?从iOS5开始你就可以改变导航栏的背景图片、tintcolor或者标题文本。
这里我们将介绍如何在Xcode中定制导航栏。
在iOS5及之后的版本,UINavigationController提供了initWithNavigationBarClass:toolbarClass:方法在导航控制器中使用定制的导航栏和工具栏子类。
在iOS4版本中,可以通过XIB的方式设置UINavigationController中导航栏子类,具体见这里。
二. iOS5及以后版本中定制导航栏背景
从iOS5开始,UINavigationBar默认不再调用drawRect:方法,如 iOS SDK Release Notes for iOS 5.0中所述。
In iOS 5, the UINavigationBar, UIToolbar, and UITabBar implementations have changed so that the drawRect: method is not called unless it is implemented in a subclass. Apps that have re-implemented drawRect: in a category on any of these classes will find that the drawRect: method isn’t called. UIKit does link-checking to keep the method from being called in apps linked before iOS 5 but does not support this design on iOS 5 or later. Apps can either:
Use the customization API for bars in iOS 5 and later, which is the preferred way.
Subclass UINavigationBar (or the other bar classes) and override drawRect: in the subclass.
iOS5版本中UINavigationBar类中不再调用drawRect:方法,但在UINavigationBar子类中drawRect:仍会被调用。所以上述通过类别(Category)扩展重写drawRect:的方法失效,而创建UINavigationBar子类的方法仍然有效,但仍然是不建议使用。
从iOS5版本来时,新增了可定制导航栏Appearance的一系列API,如可通过setBackgroundImage:forBarMetrics:设置单个导航栏的背景。
也可通过[UINavigationBar appearance]获取appearance代理来设置所有导航栏的背景,具体可见UIAppearance Protocol Reference
这样,在iOS5中可以使用Appearance API,在ViewDidLoad:中修改单个导航栏的背景
- (void)viewDidLoad{ [super viewDidLoad]; UINavigationBar *navBar = self.navigationController.navigationBar; if ([navBar respondsToSelector:@selector(setBackgroundImage:forBarMetrics:)]) { [navBar setBackgroundImage:[UIImage imageNamed:@"NavBar"] forBarMetrics:UIBarMetricsDefault]; }}
三.兼容iOS4和iOS5的处理方法
对于同时需要支持iOS4及iOS5版本的APP来说,则需要同时使用重写drawRect:和使用Appearance的方式,而且导航栏的背景图应该是可以随时配置的。所以我们将上述两种方法都集成到到类别(Category)扩展中,使用Associative References来存储设置的背景图。代码如下:
#import "UINavigationBar+CustomBackground.h"#import <objc/runtime.h>static char backgroundImageKey;@implementation UINavigationBar (CustomBackground)// iOS5 之前的版本调用- (void)drawRect:(CGRect)rect { UIImage *image = objc_getAssociatedObject(self, &backgroundImageKey); if (!image) { image = [UIImage imageNamed:@"NavBar"]; objc_setAssociatedObject(self, &backgroundImageKey, image, OBJC_ASSOCIATION_RETAIN); } [image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)]; return;}-(void)setBackgroundImage:(UIImage *)backgroundImage;{ if ([self respondsToSelector:@selector(setBackgroundImage:forBarMetrics:)]) { [self setBackgroundImage:backgroundImage forBarMetrics:UIBarMetricsDefault]; }else{ objc_setAssociatedObject(self, &backgroundImageKey, backgroundImage, OBJC_ASSOCIATION_RETAIN); [self setNeedsDisplay]; }}-(UIImage*)backgroundImage{ if ([self respondsToSelector:@selector(backgroundImageForBarMetrics:)]) { return [self backgroundImageForBarMetrics:UIBarMetricsDefault]; }else{ return objc_getAssociatedObject(self, &backgroundImageKey); }}@end
增加上述代码到项目后,我们可以使用[navBar setBackgroundImage:image]设置导航栏背景图。
对于iOS4,此方法会将image保存到关联对象中,然后调用setNeedsDisplay要求导航栏重绘,在重绘调用drawRect:时新的导航栏背景就会生效;
对应iOS5及以上版本,则直接使用setBackgroundImage:forBarMetrics:设置背景图。
四.在不同页面切换导航栏背景
有时需要在同一个Navigation Controller下各个子页面使用不同的导航栏背景,但所对应的导航栏对象实际只有一个,这就涉及到导航栏背景图的保存、更改与恢复。比如有这样的需求,在Navigation Controller下有三个子页面:MainViewController、ViewController2、ViewController3,其中MainViewController和ViewController3的导航栏背景为默认值,即上述代码中的NavBar,ViewController2的导航栏背景图为BlackNavBar;在ViewController2上还可以present一个新页面PresentViewController。
MainViewController,ViewController3的导航栏背景为默认,其代码不用有任何修改。ViewController2的导航栏背景为新背景图,则需要做导航背景图的保存,设置及恢复操作
1.保存导航栏背景
在viewDidLoad方法中保存原始的导航栏背景。
@implementation ViewController2{ UIImage *savedNavBarImage;}- (void)viewDidLoad{ [super viewDidLoad]; savedNavBarImage = [self.navigationController.navigationBar backgroundImage]; ...}
2.设置新导航栏背景
每次viewWillAppear:时设置新导航背景图。
-(void)viewWillAppear:(BOOL)animated{ [super viewWillAppear:animated]; [self.navigationController.navigationBar setBackgroundImage:[UIImage imageNamed:@"BlackNavBar"]];}
3.恢复导航栏背景
重点在于何时恢复导航栏背景,在Navigation Controller push一个新页面或者当前页面被pop的时候,需要恢复导航栏背景。而在当前页面上present一个新页面时不能修改导航栏。
当Navigation Controller pop或push时,在当前页面的viewWillDisappear:方法中Navigation Controller的viewControllers已更新,通过判断当前Navigation Controller的viewControllers的内容可以区分出当前页面消失时是在进行pop、push操作还是在进行present操作。
-(void)viewWillDisappear:(BOOL)animated{ [super viewWillDisappear:animated]; NSUInteger index = [self.navigationController.viewControllers indexOfObject:self]; if (index == NSNotFound || index == self.navigationController.viewControllers.count-2) {//pop 或者push [self.navigationController.navigationBar setBackgroundImage:savedNavBarImage]; }}
上述代码有一个假定,即在viewWillDisappear:时self.navigationController仍然指向当前的Navigation Controller,没有被置nil。但实际并非如此,在iOS4,iOS5版本中,以非动画的方式pop时(即调用popViewControllerAnimated:或popToRootViewControllerAnimated:方法时,传递参数为NO)在当前页面的viewWillDisappear:时self.navigationController为nil;在这种情况下需要通过其他方式获取到当前的Navigation Controller。
在上述情况下,尽管self.navigationController为nil,在self.view.superView仍然指向Navigation Controller中的view,我们可通过self.view.superView定位到其所属的viewController,即为当前的Navigation Controller。
通过view获取到其所属的viewContoller可通过向上逐级遍历nextResponder的方式实现,如下扩展UIView:
@implementation UIView (ViewController)-(UIViewController*)viewController{UIResponder *responder = [self nextResponder];while (responder) {if ([responder isKindOfClass:[UIViewController class]]) {return (UIViewController*)responder;}responder = [responder nextResponder];}return nil;}@end
这样,上述恢复导航栏背景的代码可修改为:
-(void)viewWillDisappear:(BOOL)animated{ [super viewWillDisappear:animated]; UINavigationController *navController = self.navigationController; //hack:ios5及之前版本在非动画方式pop时self.navigationController为nil,通过其他途径获取导航控制器 if (!navController) { UIViewController *parentController = [self.view.superview viewController]; if ([parentController isKindOfClass:[UINavigationController class]]) { navController = (UINavigationController*)parentController; } } NSUInteger index = [navController.viewControllers indexOfObject:self]; if (index == NSNotFound || index == self.navigationController.viewControllers.count-2) {//pop 或者push [navController.navigationBar setBackgroundImage:savedNavBarImage]; }}
五.iOS6下的状态栏颜色
在iOS6下,如果statusBarStyle为UIStatusBarStyleDefault的话,则状态栏的颜色会自动随着导航栏的颜色变化而变化,其颜色为导航栏的平均颜色;如果修改StatusBarStyle为UIStatusBarStyleBlackOpaque或UIStatusBarStyleBlackTranslucent后就固定为不透明黑色和透明黑色,不再随导航栏变化了。
六.参考代码
https://github.com/xuguoxing/customNavigationBar
参考
- UINavigationController Class Reference
- UINavigationBar Class Reference
- UIViewController Class Reference
- iOS SDK Release Notes for iOS 5.0
- UINavigationBar’s drawRect is not called in iOS 5.0
- Set a custom subclass of UINavigationBar in UINavigationController programmatically
- Set background image of an UINavigationBar
- viewWillDisappear: Determine whether view controller is being popped or is showing a sub-view controller
- iOS中定制导航栏背景
- iOS中定制导航栏背景
- iOS 导航栏定制
- iOS 【UIKit-UINavigationItem 定制导航栏中元素的信息】
- IOS中导航的返回按钮定制
- IOS 自定义导航栏背景
- ios开发中导航栏背景的出图规格
- iOS开发之导航栏的定制
- iOS 更改导航栏背景颜色
- ios开发:导航栏navigationbar背景渐变
- ios修改导航栏的背景颜色
- IOS 设置导航栏背景颜色
- iOS7中定制导航栏和状态栏
- ios 定制iOS 7中的导航栏和状态栏
- IOS开发 IOS7中使用图片作为导航栏返回按钮的背景
- 定制iOS 7中的导航栏和状态栏
- 定制iOS 7中的导航栏和状态栏
- (转)定制iOS 7中的导航栏和状态栏
- C++第二课
- 删除字符串中的数字和空格
- C语言一些容易出错的优先级问题
- 《算法竞赛-训练指南》第二章-2.5_UVa 11361
- HDU 4336 概率DP求期望(or容斥原理)
- iOS中定制导航栏背景
- 《我的都一本c++书》学习笔记:STL之shared_ptr(下)
- Ubuntu 文件夹丢失左边的侧边栏(设备 计算机 网络) 解决办法
- 函数复写的简单示例
- 函数指针--Nginx和Redis中两种回调函数写法
- 虚拟光驱
- hdu 4652 经典概率题+公式推导
- 深入浅出Node.js(一):什么是Node.js
- socket:bind: Address already in use 端口立即重用