状态栏风格及用法简述

现存的 iOS 7/8/9 采用了沉浸式状态栏设计,而且状态栏风格主要以黑白二色为主,比如礼物说佳学两款 APP 分别采用了这两种不同的设计。

Note: 一般而言,深色背景导航栏会搭配白色状态栏,浅色背景的则会搭配深色状态栏。

iOS 9 以前我们一般在项目 info.plist 文件中将UIViewControllerBasedStatusBarAppearance键设为 NO,然后在适当的视图控制器中使用下面的代码设置状态栏风格:

1
2
3
4
5
/// 设置白色状态栏
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];

/// 设置黑色状态栏
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleDefault];

UIViewControllerBasedStatusBarAppearance

一切似乎很美好。

然而在 iOS 9(Xcode 7)中上述方法会引起一个警告,苹果不再建议使用旧方法,我们需要重载视图控制器的+preferredStatusBarStyle方法去设置状态栏风格:

1
2
3
4
5
// Setting the statusBarStyle does nothing if your application is using the default UIViewController-based status bar system.
@available(iOS, introduced=2.0, deprecated=9.0, message="Use -[UIViewController preferredStatusBarStyle]")
public var statusBarStyle: UIStatusBarStyle
@available(iOS, introduced=2.0, deprecated=9.0, message="Use -[UIViewController preferredStatusBarStyle]")
public func setStatusBarStyle(statusBarStyle: UIStatusBarStyle, animated: Bool)

那么现在爽快地把项目 info.plist 文件中的UIViewControllerBasedStatusBarAppearance键设为 NO,或者删除吧。别忘了把你项目中的setStatusBarStyle也一并删除。我将向你演示如何优雅的设置状态栏风格。


新建一个工程,设置 view controller 背景色为水蓝色,并重载 preferredStatusBarStyle() 方法。

1
2
3
4
5
6
7
8
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.init(colorLiteralRed: 0.157, green: 0.694, blue: 1, alpha: 1)
}

override func preferredStatusBarStyle() -> UIStatusBarStyle {
return .LightContent
}

白色状态栏

效果看起来不错吧🤓。然而接下来会有一个问题,如下图。

View controller embed in a navigation controller

编译运行项目,这种情况下你会发现上述方法失效,状态栏又变黑色了:

状态栏又变黑色了

其实并不算是失效了,我们不妨来看看preferredStatusBarStyle()的定义:

1
2
3
// These methods control the attributes of the status bar when this view controller is shown. They can be overridden in view controller subclasses to return the desired status bar attributes.
@available(iOS 7.0, *)
public func preferredStatusBarStyle() -> UIStatusBarStyle // Defaults to UIStatusBarStyleDefault

大意是说当 view controller 显示的时候,这个方法可以用来控制状态栏的属性。你可以在 UIViewController 的子类中重载该方法返回你期望的状态栏属性。

那为什么我现在重载该方法却不起作用呢?事实上preferredStatusBarStyle()根本未被调用。原因能是 UINavigationController 作为一个容器“承包”了这些事情。我们所添加的 view controller 都在它的导航栈中,可以说这些控制器都是导航栏控制器的child view controller。此时导航栏控制器才是真正能操作状态栏的 Boss。

Objects managed by the navigation controller(Image from: [Apple](http://developer.apple.com))

为了验证一可能性,我把原先 view controller 中的preferredStatusBarStyle()方法删除,然后实例化一个UINavigationController的子类NavigationController,在 Storyboard 中将其关联到导航栏控制器上且设置了naviagation bar 的 bar tint color 便于区分,最后在 NavigationController 中重载preferredStatusBarStyle()方法。我终于得到了想要的结果。

在NavigationController重载preferredStatusBarStyle()方法

正确的结果

另外,我们可能遇到这样一个需求:在导航栈中的第一个 view controller 里的状态栏为白色,第二个 view controller 里的状态栏为黑色。上面这种在导航栏控制器中控制状态栏属性的一刀切的粗暴做法显然不符合我们的要求,那么这里有另外一个方法 :

1
2
3
// Override to return a child view controller or nil. If non-nil, that view controller's status bar appearance attributes will be used. If nil, self is used. Whenever the return values from these methods change, -setNeedsUpdatedStatusBarAttributes should be called.
@available(iOS 7.0, *)
public func childViewControllerForStatusBarStyle() -> UIViewController?

大意是,如果这个方法返回值为 non-nil,则将更改状态栏属性的控制权移交给你返回的那个控制器。如果返回值为 nil 或者不重载该方法,那么由自己负责控制状态栏的属性。当状态栏的样式被更改之前,该控制器的-setNeedsUpdatedStatusBarAttributes方法应该被调用。

前面几句很好理解,最后一句是什么意思呢?

如果在该 UIViewControlle r已经在显示在当前,你可能还要在当前页面不时的更改状态栏的 style,那么你需要先调用-setNeedsStatusBarAppearanceUpdate方法(它通知系统去调用当前UIViewController 的-preferredStatusBarStyle方法)。

要实现我们的需求,就只能在NavigationController中重载-childViewControllerForStatusBarStyle方法,既然它返回一个视图控制器的实例,那么我们只要将导航栈中的topViewController作为返回值(该方法会被调用多次,且每次~~设置状态栏 style ~~push/pop view controller 该方法都会被调用),然后在需要设置状态栏 style 的 view controller 中像最开始一样重载-preferredStatusBarStyle方法即可。

1
2
3
override func childViewControllerForStatusBarStyle() -> UIViewController? {
return self.topViewController
}

最终效果