iOS中定制导航栏背景

一.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/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值