现在的位置: 首页 > 综合 > 正文

iOS中定制导航栏背景

2013年12月11日 ⁄ 综合 ⁄ 共 7200字 ⁄ 字号 评论关闭

一.iOS4中定制导航栏背景

在iOS4中通过重写UINavigationBardrawRect:方法,可以修改导航栏的背景。

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下,如果statusBarStyleUIStatusBarStyleDefault的话,则状态栏的颜色会自动随着导航栏的颜色变化而变化,其颜色为导航栏的平均颜色;如果修改StatusBarStyle为UIStatusBarStyleBlackOpaque或UIStatusBarStyleBlackTranslucent后就固定为不透明黑色和透明黑色,不再随导航栏变化了。

六.参考代码

https://github.com/xuguoxing/customNavigationBar

参考

  1. UINavigationController Class
    Reference
  2. UINavigationBar Class Reference
  3. UIViewController Class Reference
  4. iOS SDK Release Notes for iOS 5.0
  5. UINavigationBar’s drawRect is not called in iOS 5.0
  6. Set a custom subclass of UINavigationBar
    in UINavigationController programmatically
  7. Set background image of an UINavigationBar
  8. viewWillDisappear: Determine
    whether view controller is being popped or is showing a sub-view controller

本文出自 清风徐来,水波不兴 的博客,转载时请注明出处及相应链接。

本文永久链接: http://www.winddisk.com/2013/06/24/ios%e4%b8%ad%e5%ae%9a%e5%88%b6%e5%af%bc%e8%88%aa%e6%a0%8f%e8%83%8c%e6%99%af/

抱歉!评论已关闭.