17
votes

I need to let a specific ViewController embedded in an UINavigationController to have light status bar text color (but other ViewControllers to behave differently). I am aware of at least 3 methods, none of which however work in my case.

  1. How to change Status Bar text color in iOS 7, the method is primarily:

    • Set the UIViewControllerBasedStatusBarAppearance to YES in the plist
    • In viewDidLoad do a [self setNeedsStatusBarAppearanceUpdate];
    • Add the following method:

      - (UIStatusBarStyle)preferredStatusBarStyle{ 
            return UIStatusBarStyleLightContent; 
        }
      

    Running on iOS 7.0.3, this method does not work for me, since even after I have implemented all 3 steps correctly, preferredStatusBarStyle is never called.

  2. UIStatusBarStyle PreferredStatusBarStyle does not work on iOS 7, the method is primarily:

    Setting your navigationBar’s barStyle to UIBarStyleBlackTranslucent will give white status bar text (ie. UIStatusBarStyleLightContent), and UIBarStyleDefault will give black status bar text (ie. UIStatusBarStyleDefault).

    This method works fair and square on iPhone, but not on iPad.

  3. Setting the UIViewControllerBasedStatusBarAppearance to NO in the plist, and use

    [[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];
    

    This clearly doesn't apply in this case, since I need to only specify different status bar colors for two of the ViewControllers.

Thanks for all help!

8

8 Answers

14
votes

For people having this problem with a UINavigationController I can recommend creating a custom UINavigationController and implementing the preferredStatusBarStyle on it like this:

- (UIStatusBarStyle)preferredStatusBarStyle
{
    return [self.topViewController preferredStatusBarStyle];
}

That way the statusbar style will be that of the top view controller. Now you can implement the view controller's preferredStatusBarStyle anyway you like.

8
votes

Here's an improvement to Groot answer, in form of a simple category to UINavigationController, without the need to subclass UINavigationController.

Swift

extension UINavigationController {
    override public func preferredStatusBarStyle() -> UIStatusBarStyle {
        return topViewController?.preferredStatusBarStyle() ?? .Default
    }
}

Swift 3 & Swift 4

extension UINavigationController {
    open override var preferredStatusBarStyle: UIStatusBarStyle {
        return topViewController?.preferredStatusBarStyle ?? .default
    }
}

Objective-C

@implementation UINavigationController (StatusBarStyle)

- (UIStatusBarStyle)preferredStatusBarStyle 
{
    return [self.topViewController preferredStatusBarStyle];
}

@end
6
votes

To set UIStatusBarStyle individually for each UIViewController on UINavigationController stack you have to first subclass your UINavigationController and override childViewControllerForStatusBarStyle method.

In your UINavigationController subclass add:

-(UIViewController *)childViewControllerForStatusBarStyle {
     return self.visibleViewController;
}

than you can set UIStatusBarStyle to whatever you want in every UIViewController using preferredStatusBarStyle method. Eg:

-(UIStatusBarStyle)preferredStatusBarStyle {
     return UIStatusBarStyleLightContent;
}
3
votes

iOS 13 Solution(s)

Regarding your attempt #3 - DEPRECATED

UIApplication.setStatusBarStyle(_:animated:) has been deprecated since iOS 9. According to Apple,

In iOS 7 and later, status bar behavior is determined by view controllers, and so calling this method has no effect by default. When view controller-based status bar appearance is disabled, this method behaves normally. To opt out of the view controller-based status bar appearance behavior, you must add the UIViewControllerBasedStatusBarAppearance key with a value of false to your app’s Info.plist file, but doing so is not recommended.

Regarding your attempt #2 - LEGACY

Setting the barStyle property is now (iOS 13+) considered a "legacy customization." According to Apple,

In iOS 13 and later, customize your navigation bar using the standardAppearance, compactAppearance, and scrollEdgeAppearance properties. You may continue to use these legacy accessors to customize your navigation bar's appearance directly, but you must update the appearance for different bar configurations yourself.

Regarding your attempt #1 - You were on the right track!

UINavigationController is a subclass of UIViewController (who knew 🙃)!

Therefore, when presenting view controllers embedded in navigation controllers, you're not really presenting the embedded view controllers; you're presenting the navigation controllers! UINavigationController, as a subclass of UIViewController, inherits preferredStatusBarStyle and childForStatusBarStyle, which you can set as desired.

Any of the following methods should work:

  1. Override preferredStatusBarStyle within UINavigationController

    • preferredStatusBarStyle (doc) - The preferred status bar style for the view controller
    • Subclass or extend UINavigationController

      class MyNavigationController: UINavigationController {
          override var preferredStatusBarStyle: UIStatusBarStyle {
              .lightContent
          }
      }
      

      OR

      extension UINavigationController {
          open override var preferredStatusBarStyle: UIStatusBarStyle {
              .lightContent
          }
      }
      
  2. Override childForStatusBarStyle within UINavigationController

    • childForStatusBarStyle (doc) - Called when the system needs the view controller to use for determining status bar style
    • According to Apple's documentation,

      "If your container view controller derives its status bar style from one of its child view controllers, [override this property] 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."

    • In other words, if you don't implement solution 3 here, the system will fall back to solution 2 above.
    • Subclass or extend UINavigationController

      class MyNavigationController: UINavigationController {
          override var childForStatusBarStyle: UIViewController? {
              topViewController
          }
      }
      

      OR

      extension UINavigationController {    
          open override var childForStatusBarStyle: UIViewController? {
              topViewController
          }
      }
      
    • You can return any view controller you'd like above. I recommend one of the following:

      • topViewController (of UINavigationController) (doc) - The view controller at the top of the navigation stack
      • visibleViewController (of UINavigationController) (doc) - The view controller associated with the currently visible view in the navigation interface (hint: this can include "a view controller that was presented modally on top of the navigation controller itself")

Note: If you decide to subclass UINavigationController, remember to apply that class to your nav controllers through the identity inspector in IB.

P.S. My code uses Swift 5.1 syntax 😎

0
votes

I used the first method you mentioned, I also found there's kinda bug when you used UINavigationController, it will never pass preferredStatusBarStyle call to it's child view controllers. What I have done is subclass the UINavigationController, and override preferredStatusBarStyle method as follows:

@implementation GLBaseNavigationController

- (UIStatusBarStyle)preferredStatusBarStyle
{
    UIViewController *lastViewController = [self.viewControllers lastObject];
    if ([lastViewController respondsToSelector:@selector(preferredStatusBarStyle)]) {
        return [lastViewController preferredStatusBarStyle];
    } else if ([super respondsToSelector:@selector(preferredStatusBarStyle)]) {
        return [super preferredStatusBarStyle];
    }
    return UIStatusBarStyleDefault;
}

Then whenever I need a navigation controller, I use GLBaseNavigationController instead of UINavigationController. For storyboards, you need to specify the class of the navigation controller to your subclass as well.

0
votes

For your first solution, I don't think you can change the status bar in viewDidLoad. If you have two ViewControllers stacked on top of each other, and each one toggles the status bar differently, that method will only get called once for each. You really want to change the status bar in viewWillAppear so that it gets called each time the page is shown. I also don't think you can rely on preferredStatusBarStyle since I'm also not sure how often/when that gets called. This is how you want to do it:

- (void) viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    [self.navigationController.navigationBar setBarStyle:UIBarStyleDefault];
}
-3
votes

Currently you can only do light and dark. To change to light do.

  1. Set the UIViewControllerBasedStatusBarAppearance to YES in the .plist file.

  2. In the viewDidLoad method do [self setNeedsStatusBarAppearanceUpdate];

  3. Add the this method:

-(UIStatusBarStyle)preferredStatusBarStyle{ return UIStatusBarStyleLightContent; }

To change it back to dark change the UIStatusBarStyleLightContent to UIStatusBarStyleDefault

-3
votes

In your AppDelegate didFinishLaunch method, set the default status bar style, say:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleDefault
                                                animated:YES];       
    return YES;
}

Then, in your those two view controllers, where you want to change status bar, override following methods:

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated]        

    // Here change status bar color
    [[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent
                                                animated:YES];       

}

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear]                

    // Here bring back to color, that we set in AppDelegate
    [[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleDefault
                                                animated:YES];       
}