iOS 正确设置状态栏 Style

白色文字状态栏
黑色文字状态栏

iOS 的状态栏

自 iOS 7 以来,iOS 采用了沉浸式状态栏设计,而且状态栏风格主要以黑白二色为主,比如礼物说佳学(已解散)两款 APP 分别采用了这两种不同的设计。

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

iOS 9 以前我们一般在项目 info.plist 文件中将UIViewControllerBasedStatusBarAppearance键设为 NO,

UIViewControllerBasedStatusBarAppearance

然后在适当的视图控制器中使用下面的代码设置状态栏风格:

/// 设置白色状态栏
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];

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

然而这样的代码结构扰乱了我们对状态栏控制的节奏。想象一下,如果项目中的状态栏状态变化非常频繁,你不得不在各种 ViewController下更新和恢复状态栏的 style 状态。事实上我们需要的仅仅只是每一个 ViewController 和对应的 StatusBarStyle 相关联。我们不需要全局修改,全局意味着混乱。

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

// 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也一并删除。这里将向你演示如何优雅的设置状态栏风格。


Case 1: Single view application

新建一个 Single view application project,设置 view controller 背景色为水蓝色,并重写preferredStatusBarStyle属性。

override func viewDidLoad() {
    super.viewDidLoad()
    self.view.backgroundColor = UIColor.init(colorLiteralRed: 0.157, green: 0.694, blue: 1, alpha: 1)
}

override var preferredStatusBarStyle: UIStatusBarStyle {
    return .lightContent
}
白色状态栏

效果看起来不错。

Case 2: View controller embed in a navigation controller

但假如我的 View Controller 位于一个导航栈中,状态栏又变黑色了:

View controller embed in a navigation controller

状态栏又变黑色了

我们不妨来看看preferredStatusBarStyle()的定义:

// 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, *)
open var preferredStatusBarStyle: UIStatusBarStyle { get } // Defaults to UIStatusBarStyleDefault

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

那为什么我现在重载该属性却不起作用呢?事实上preferredStatusBarStyle()并未被调用。因为当前 view controller 位于一个容器类 controller 中。容器类 controller 包括UINavigationVontrollerUITabBarController等。这种情况下,状态栏的风格会交由容器管理,你只需要在容器 controller 中重载改属性即可。(下图中代码为 Swift 2)

在NavigationController重载preferredStatusBarStyle()方法
正确的结果

Case3: Switch style

我们可能遇到这样一个需求,在导航栈中的第一个 view controller 里的状态栏为白色,第二个 view controller 里的状态栏为黑色。上面这种在导航栏控制器中控制状态栏属性的做法显然不符合我们的要求。所幸 iOS SDK 为我们提供了另一个属性:

// 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, *)
open var childViewControllerForStatusBarStyle: UIViewController? { get }

大意是,如果这个方法返回值为 non-nil,则将更改状态栏属性的控制权移交给你返回的那个控制器。如果返回值为 nil 或者不重载该方法,那么由自己负责控制状态栏的属性。每当状态栏的样式被更改时,你都应该该调用控制器的-setNeedsUpdatedStatusBarAttributes方法。最后一句是说,如果你要在当前页面不时的更改状态栏的 style,那么你需要先调用-setNeedsStatusBarAppearanceUpdate方法(它通知系统去重新获取当前UIViewController 的preferredStatusBarStyle)。

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

override var childViewControllerForStatusBarStyle: UIViewController? {
    return self.topViewController
}
最终效果

Case4: Present Modal Controller

需要指出的是,如果一个 view controller 被 present 出来,通常来说这个 view controller 会忽略preferredStatusBarStyle属性,你需要在它被 present 之前设置这样一个属性:

vc.modalPresentationCapturesStatusBarAppearance = true
// This controls whether this view controller takes over control of the status bar's appearance when presented non-full screen on another view controller. Defaults to NO.
@available(iOS 7.0, *)
open var modalPresentationCapturesStatusBarAppearance: Bool

你可能感兴趣的:(iOS 正确设置状态栏 Style)