一. 前言

iOS开发过程中经常会修改导航栏的背景色(或者设置透明渐变),为了适配背景色,需要对状态栏的颜色进行变更,本文主要介绍对状态栏颜色的变更。

二. 正文

状态栏颜色变更可以理解为其样式的变更,通过苹果API可知状态栏有以下几种样式:

1
2
3
4
5
6
7
typedef NS_ENUM(NSInteger, UIStatusBarStyle) {
UIStatusBarStyleDefault = 0, // Dark content, for use on light backgrounds
UIStatusBarStyleLightContent NS_ENUM_AVAILABLE_IOS(7_0) = 1, // Light content, for use on dark backgrounds

UIStatusBarStyleBlackTranslucent NS_ENUM_DEPRECATED_IOS(2_0, 7_0, "Use UIStatusBarStyleLightContent") = 1,
UIStatusBarStyleBlackOpaque NS_ENUM_DEPRECATED_IOS(2_0, 7_0, "Use UIStatusBarStyleLightContent") = 2,
} __TVOS_PROHIBITED;

后两种已经弃用,主要是下面两种:

  1. UIStatusBarStyleDefault:默认的黑色字体内容,主要用与浅色背景。
  2. UIStatusBarStyleLightContent:默认的浅色内容(白色),主要用于深色背景。

    设置APP启动时状态栏状态

    在info.plist文件中,开发者可以设置UIStatusBarStyle和UIStatusBarHidden选项,该选项说明如下:
    info.plist

默认情况下:

  1. UIStatusBarStyle值为:UIStatusBarStyleDefault
  2. UIStatubBarHidden值为:NO

开发者可以在Info.plist中进行设置两个值来控制在启动页展示的时候状态栏是否隐藏以及其Style。接下来需要关注另外一个key:UIViewControllerBasedStatusBarAppearance。该key的作用是指示状态栏的外观是否基于当前控制器的样式。该值默认为YES,如果设置为NO,则在Info.plist中设置的UIStatusBarStyle和UIStatubBarHidden就会状态保持。也就是说如果为NO,并且设置UIStatubBarHidden为NO,那APP启动之后是不展示状态栏的。如果UIViewControllerBasedStatusBarAppearance设置为YES或者不设置(默认为YES),在Info.plist中设置只会影响APP启动时状态栏的样式,不会影响启动之后进入首页的样式。

UIViewControllerBasedStatusBarAppearance (Boolean - iOS) Specifies whether the status bar appearance is based on the style preferred by the view controller that is currently under the status bar. When this key is not present or its value is set to YES, the view controller determines the status bar style. When the key is set to NO, view controllers (or the app) must each set the status bar style explicitly using the UIApplication object.

This key is supported in iOS 7.0 and later.状态栏UIStatusBarStyle设置

设置状态栏样式

在程序进入前台之后,开发者可以通过下面两种方式更改状态栏的状态:

第一种方式:通过setStatusBarStyle设置

直接使用UIApplication提供的方法进行设置:

1
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];

运行之后发现状态栏内容颜色依然为黑色,为什么没有生效呢?原来是需要在info.plist中设置UIViewControllerBasedStatusBarAppearance为NO。设置为NO之后,该方式才能设置成功。该方式有两个问题:

  1. 该设置方式在iOS9之后就被弃用了,苹果建议使用preferredStatusBarStyle进行设置。当然如果你的APP想要支持iOS9之前的版本,则需要对版本进行判断。
  2. 如果只想为某个页面设置UIStatusBarStyleLightContent,入口页面保持不变,则需要对状态进行还原。即在特定VC中的viewWillAppear中设置为UIStatusBarStyleLightContent,在viewWillDisappear中设置为UIStatusBarStyleDefault。否则将会影响其他页面状态栏样式展示。

第二种方式:通过重写preferredStatusBarStyle设置

通过这种方式设置状态时,需要将UIViewControllerBasedStatusBarAppearance设置为YES(或者不设置,默认为YES)。然后在VC中重写preferredStatusBarStyle:

1
2
3
- (UIStatusBarStyle)preferredStatusBarStyle {
return UIStatusBarStyleLightContent;
}

运行程序发现不生效。看下该方法的说明:

You can override the preferred status bar style for a view controller by implementing the childViewControllerForStatusBarStyle method.

If the return value from this method changes, call the setNeedsStatusBarAppearanceUpdate method.

看说明是需要实现childViewControllerForStatusBarStyle方法。看到childViewController,一般会联想到navigationController和tabBarController,他们都有自己的childViewController。因为测试的Demo是有navigation导航的,有没有可能是因为需要重写一下navigationController的看说明是需要实现childViewControllerForStatusBarStyle方法呢?因此做了如下尝试:将ViewController直接设置为window的rootVC,再次运行,发现生效了。因此可能是由navigationController导致的问题。
接下来实现NavigationController的子类,然后在子类中实现如下方法:

1
2
3
- (UIViewController *)childViewControllerForStatusBarStyle {
return self.topViewController;
}

然后再次运行程序,发现这次在ViewController中设置的样式生效了。经过测试发现:如果在navigationController中不实现childViewControllerForStatusBarStyle,则会调用navigationController的preferredStatusBarStyle。如果实现了该方法,该方法会返回导航栈最顶层的VC。

If your container view controller derives its status bar style from one of its child view controllers, implement this method and return that child view controller. If you return nil or do not override this method, the status bar style for self is used. If the return value from this method changes, call the setNeedsStatusBarAppearanceUpdate method.

从childViewControllerForStatusBarStyle文档可知,如果开发者想要通过navigationController的子VC来重写导航栈,那就需要通过该方法将特定的VC返回。这里总是返回最顶层的VC。
因此如果发现带导航栈APP的preferredStatusBarStyle不生效,可以对childViewControllerForStatusBarStyle进行重写。
状态栏的隐藏方法prefersStatusBarHidden和preferredStatusBarStyle类似,也是需要实现childViewControllerForStatusBarHidden。这里就不再赘述。
这里需要注意的是:当在VC中对状态栏的状态进行重设(prefersStatusBarHidden 或者preferredStatusBarStyle返回值有所变更)每次对状态栏状态进行变更,都需要调用setNeedsStatusBarAppearanceUpdate方法,该方法会告诉系统,VC的状态栏状态发生改变,进而会重新调用prefersStatusBarHidden或者preferredStatusBarStyle。

注意点

  1. 每次状态栏状态之后,需要调用一下setNeedsStatusBarAppearanceUpdate方法。
  2. 如果是UITabBarController,也需要实现childViewControllerForStatusBarStyle(或者childViewControllerForStatusBarHidden)来指定特定VC(TabBarController中重写childViewControllerForStatusBar 返回tabBarController的selectedViewController )。

参考

  1. UIViewControllerBasedStatusBarAppearance
  2. Information Property List Key Reference
  3. https://stackoverflow.com/questions/45372550/preferredstatusbarstyle-is-not-working