参考文章
navigationItem
UINavigationItem
UINavigationBar
UIBarButtonItem
UIButton
iOS 7 教程:定制iOS 7中的导航栏和状态栏
前言
本文试图阐释清楚导航栏相关的概念和用法,比如UINavigationBar
和UINavigationItem
的区别和联系,UIBarButtonItem
的用法以及在纯代码和storyboard中有什么不同。如果读者有类似的疑惑,不妨读一读本文。
本文撰写时,用的iOS8.3、Xcode6.3,因为没有仔细考证iOS各版特性的不同,可能导致出入,若读者遇到,还请指出,我及时改正。
1、UINavigationBar VS UINavigationItem
文档说明:
The UINavigationBar class provides a control for navigating hierarchical content. It’s a bar, typically displayed at the top of the screen, containing buttons for navigating within a hierarchy of screens. The primary properties are a left (back) button, a center title, and an optional right button. You can use a navigation bar as a standalone object or in conjunction with a navigation controller object.
翻译:
UINavigationBar类提供一种对导航层级内容的控制。它是一个栏,最典型的用法就是放在屏幕顶端,包含着各级视图的导航按钮。它最首要的属性是左按钮(返回按钮)、中心标题,还有可选的右按钮。你可以单独用导航栏,或者和导航控制器一起使用。
文档说明:
A UINavigationItem object manages the buttons and views to be displayed in a UINavigationBar object. When building a navigation interface, each view controller pushed onto the navigation stack must have a UINavigationItem object that contains the buttons and views it wants displayed in the navigation bar. The managing UINavigationController object uses the navigation items of the topmost two view controllers to populate the navigation bar with content.
翻译:
一个UINavigationItem对象管理展示在导航栏上的按钮和视图。当创建一个导航界面的时候,每个压入导航栈中的视图控制器都需要一个navigation item,它包含了展示在导航栏上的按钮和视图。导航控制器利用最顶层的两个视图控制器的navigation item来提供导航栏的内容。
在纯代码操作UINavigationBar和UINavigationItem的实例中,我们会觉得不舒服,或者说疑惑的地方
事实上,UINavigationController并没有
navigationItem
这样一个直接的属性,由于UINavigationController继承于UIViewController,而UIViewController是有
navigationItem
这个属性的,所以才会出现如图所示的情况,如果你这样用:
self.navigationController.navigationItem.title = @"刘大帅";
是没有任何效果的。这当然是由于UINavigationController是个特殊的视图控制器,它是视图控制器的容器(另外两个容器是UITabBarController和UISplitViewController),你不应该把它当一般的UIViewController来使用.
另外,让人觉得不爽的地方如下:
self.navigationItem.title = @"刘大帅";
self.navigationController.navigationBar.barTintColor = [UIColor purpleColor];
效果如下:
这里让人迷惑的地方在于,同样是对导航栏的操作,怎么一个在第一层级(UIViewController),另外一个在其属性navigationController的层级。
如前所说,navigationItem
是UIViewController的一个属性,开发者文档是这样描述这个属性的:
This is a unique instance of UINavigationItem created to represent the view controller when it is pushed onto a navigation controller. The first time the property is accessed, the UINavigationItem object is created. Therefore, you should not access this property if you are not using a navigation controller to display the view controller. To ensure the navigation item is configured, you can either override this property and add code to create the bar button items when first accessed or create the items in your view controller'��s initialization code.
Avoid tying the creation of bar button items in your navigation item to the creation of your view controller'��s view. The navigation item of a view controller may be retrieved independently of the view controller'��s view. For example, when pushing two view controllers onto a navigation stack, the topmost view controller becomes visible, but the other view controller'��s navigation item may be retrieved in order to present its back button.
The default behavior is to create a navigation item that displays the view controller'��s title.
翻译一下:
它是UINavigationItem一个独特的实例。当视图控制器被推到导航控制器中时,它来代表这个视图控制器。当第一次访问这个属性的时候,它会被创建。因此,如果你并没有用导航控制器来管理视图控制器,那你不应该访问这个属性。为确保navigation item 已经配置,你可以在视图控制器初始化时,重写这个属性、创建bar button item。
要避免在创建视图控制器的视图时,创建bar button item。视图控制器的这个属性——navigationItem,它的恢复(生命周期——作者注),可能独立于视图控制器的视图。为什么会这样?举例来说,当把两个视图控制器压到导航栈中,最顶层的视图控制器是可见的,但另一个视图控制器的navigation item 可能是活跃状态(此时,隐藏的视图控制器的视图肯定是不活跃的,所以,这个时候navigation item 是独立于视图控制器的视图的——作者注),因为它要呈现其返回按钮。
缺省行为是创建一个navigation item 来展示视图控制器的标题。
我们来总结一下,如果把导航控制器比作一个剧院,那导航栏就相当于舞台,舞台必然是属于剧院的,所以,导航栏是导航控制器的一个属性。视图控制器(UIViewController)就相当于一个个剧团,而导航项(navigation item)就相当于每个剧团的负责人,负责与剧院的人接洽沟通。显然,导航项应该是视图控制器的一个属性。虽然导航栏和导航项都在做与导航相关的事情,但是它们的从属是不同的。
我想,这个类比应该能解决以上的疑惑吧。导航栏相当于负责剧院舞台的布景配置,导航项则相当于协调每个在舞台上表演的演员(bar button item,title 等等),每个视图控制器的导航项可能都是不同的,可能一个右边有一个选择照片的bar button item,而另一个视图控制器的右边有两个bar button item。
2、关于UINavigationItem一些测试
-
我们知道navigation item 有
leftBarButtonItems
和rightBarButtonItems
两个属性,每个属性都可以赋值一个装有UIBarButtonItem
对象的数组,有没有想过,如果数组装有很多UIBarButtonItem
对象,超过了导航栏展现的极限,会怎样?如下图:
代码:
NSMutableArray* array = [NSMutableArray array];
for (int i =0; i<7; i++) {
UIBarButtonItem* item = [[UIBarButtonItem alloc]initWithTitle:[NSString stringWithFormat:@"item%d",i+1] style:UIBarButtonItemStylePlain target:nil action:nil];
[array addObject:item];
}
self.navigationItem.leftBarButtonItems = array;
self.navigationItem.rightBarButtonItems = array;
其实,这在开发文档中已经说的很清楚了,拿leftBarButtonItems
来说:
This array can contain 0 or more bar items to display on the left side of the navigation bar. Items can include fixed-width and flexible-width spaces. If the leftItemsSupplementBackButton property is YES, the items are displayed to the right of the back button, otherwise the items replace the back button and start at the left edge of the bar. Items are displayed left-to-right in the same order as they appear in the array.
If there is not enough room to display all of the items in the array, those that would overlap the title view (if present) or the buttons on the right side of the bar are not displayed.
The first item in the array can also be set using the leftBarButtonItem property.
-
前面说过,用代码的时候,当你首次访问视图控制器中的navigation item的时候,它会自动创建,在storyboard中是怎样的呢?
答案是,你需要给导航栏中的scene添加navigation item,如图:
-
storyboard中怎样配置
leftBarButtonItems
和rightBarButtonItems
两个属性?我发现storyboard只支持左右各一个bar button item,当你拖拽一个新的bar button item到导航栏视图给它增加一个时,它只会替换,可能,如果想多个,还得用代码来实现。如图:
3、UIBarButtonItem VS UIButton
其实对于这两个,我没有深入总结。
通过这两个图,我们知道这两个家伙没什么血缘关系,有点像生物界的趋同进化,比如小熊猫和浣熊
(例子不太恰当,其实这俩动物区别挺大的……)。
我尝试过用UIButton当UIBarButtonItem使用(通过storyboard将UIButton拖拽到导航栏上,并写了响应事件),button倒是能显示出来,只是点击没反应。这倒不出乎意料,如果能当UIBarButtonItem使用,才应该出乎意料,毕竟它们除了长的样子和交互方式类似,其他并不同。
其实,我们知道UIBarButtonItem是专门给UIToolBar和UINavigationBar定制的类似button的类就好了。将来有更深的体会,我会及时更新。
4、UIToolBar VS UITabBar
这个也没什么较深的体会,先占个位置……
这里之所以提一句,是因为导航控制器带有一个toolBar的属性,在storyboard中,如果你没有给scene添加navigation item,就往scene上拖拽bar button item,它是不会落到导航栏上,而是落到toolBar上,toolBar默认是隐藏的,但在scene上它是显示出来的。
接下来,挂羊头卖狗肉,在这里结合UIToolBar,讲一个UIBarButtonItem的用法——为相邻bar button item添加间隔,通过观察,这个只在UIToolBar中有效果(在storyboard中使用的话,只能给UIToolBar添加,storyboard的对象库,也说明这是为UIToolBar准备的)。
5、导航栏一般用法集锦
对于导航栏的操作有两种方法:
- [UINavigationBar appearance]类方法
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
[UINavigationBar appearance].tintColor = [UIColor orangeColor];
[[UINavigationBar appearance] setBackgroundImage:[UIImage imageNamed:@"m_nav64"] forBarMetrics:UIBarMetricsDefault];
return YES;
}
这显然是因为UINavigationBar遵从了UIAppearance
协议的缘故。这个方法在AppDelegate中有效,在特定的视图控制器中是无效的。它应该是对所有导航栏生效的。
- self.navigationController.navigationBar 实例方法
- (void)viewDidLoad
{
self.navigationItem.title = @"刘大帅";
self.navigationController.navigationBar.tintColor = [UIColor orangeColor];
[self.navigationController.navigationBar setBackgroundImage:[UIImage imageNamed:@"m_nav"] forBarMetrics:UIBarMetricsDefault];
self.navigationController.toolbarHidden = NO;
}
这种用法只对该视图控制器的导航栏有效果,由于viewDidLoad:在application: didFinishLaunchingWithOptions:之后执行,所以它会覆盖上一种方法带来的效果。假设这样一种场景,用UITabBarController作为最外层视图控制器容器,每一个tab都有自己的一个导航栈。我们可以用第一种方法做整体效果的设计,用第二种方法作特定tab中的导航栏的设计。
注意:两个效果之所以有区别,是因为我用了不同的图片,以示区别。
下面我们以第二种方法为例来介绍导航栏的一般用法
- (void)viewDidLoad
{
//默认背景色上传到不理想,所以换一种背景色
self.view.backgroundColor = [UIColor orangeColor];
//*****************navigationItem*********************************
//navigationItem控制导航栏标题(title)、promt、标题视图(titleView)、以及按钮(barButtonItem)的添加和数量
self.navigationItem.title = @"刘大帅";
//我表示我不喜欢promt...
// self.navigationItem.prompt = @"promt";
//修改导航栏标题为图片
self.navigationItem.titleView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"m_hot60"]];
//添加多个按钮
UIBarButtonItem* item1 = [[UIBarButtonItem alloc]initWithTitle:@"item1" style:UIBarButtonItemStylePlain target:nil action:nil];
UIBarButtonItem* item2 = [[UIBarButtonItem alloc]initWithTitle:@"item2" style:UIBarButtonItemStylePlain target:nil action:nil];
NSArray* array = @[item1,item2];
self.navigationItem.leftBarButtonItems = array;
self.navigationItem.rightBarButtonItems = array;
//*****************navigationBar**********************************
//navigationBar控制导航栏背景色(barTintColor)、背景图片(backgroundImage)、按钮字体颜色(tintColor),标题文本属性(titleTextAttributes)
//调整导航栏背景色
self.navigationController.navigationBar.barTintColor = [UIColor orangeColor];
//半透明开关
self.navigationController.navigationBar.translucent = NO;
//为导航栏添加背景图片,图片如果是44高,那么不覆盖状态栏,如果是64高就会覆盖状态栏
//UIBarMetricsDefault 缺省值 UIBarMetricsCompact 横屏样式 UIBarMetricsDefaultPrompt和UIBarMetricsCompactPrompt是有promt的两种样式
[self.navigationController.navigationBar setBackgroundImage:[UIImage imageNamed:@"m_nav64"] forBarMetrics:UIBarMetricsDefault];
//
self.navigationController.navigationBar.tintColor = [UIColor purpleColor];
//定制返回按钮,这两个要一起用,为啥这么用,苹果言语不详
self.navigationController.navigationBar.backIndicatorImage = [UIImage imageNamed:@"m_ios"];
self.navigationController.navigationBar.backIndicatorTransitionMaskImage = [UIImage imageNamed:@"m_ios"];
//修改导航栏标题的字体
NSShadow *shadow = [[NSShadow alloc] init];
shadow.shadowColor = [UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.8];
shadow.shadowOffset = CGSizeMake(0, 1);
//字典中放入你想修改的键值对,原来的UITextAttributeFont、UITextAttributeTextColor、UITextAttributeTextShadowColor、UITextAttributeTextShadowOffset已弃用
self.navigationController.navigationBar.titleTextAttributes = @{NSForegroundColorAttributeName:[UIColor colorWithRed:245.0/255.0 green:245.0/255.0 blue:245.0/255.0 alpha:1.0],
NSShadowAttributeName:shadow,
NSFontAttributeName:[UIFont fontWithName:@"HelveticaNeue-CondensedBlack" size:21.0]
};
//导航栏toolBar隐藏开关
self.navigationController.toolbarHidden = NO;
}