preferredStatusBarStyle isn't called

For anyone using a UINavigationController:

The UINavigationController does not forward on preferredStatusBarStyle calls to its child view controllers. Instead it manages its own state - as it should, it is drawing at the top of the screen where the status bar lives and so should be responsible for it. Therefor implementing preferredStatusBarStyle in your VCs within a nav controller will do nothing - they will never be called.

The trick is what the UINavigationController uses to decide what to return for UIStatusBarStyleDefault or UIStatusBarStyleLightContent. It bases this on its UINavigationBar.barStyle. The default (UIBarStyleDefault) results in the dark foreground UIStatusBarStyleDefault status bar. And UIBarStyleBlack will give a UIStatusBarStyleLightContent status bar.

TL;DR:

If you want UIStatusBarStyleLightContent on a UINavigationController use:

self.navigationController.navigationBar.barStyle = UIBarStyleBlack;

Possible root cause

I had the same problem, and figured out it was happening because I wasn't setting the root view controller in my application window.

The UIViewController in which I had implemented the preferredStatusBarStyle was used in a UITabBarController, which controlled the appearance of the views on the screen.

When I set the root view controller to point to this UITabBarController, the status bar changes started to work correctly, as expected (and the preferredStatusBarStyle method was getting called).

(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    ... // other view controller loading/setup code

    self.window.rootViewController = rootTabBarController;
    [self.window makeKeyAndVisible];
    return YES;
}

Alternative method (Deprecated in iOS 9)

Alternatively, you can call one of the following methods, as appropriate, in each of your view controllers, depending on its background color, instead of having to use setNeedsStatusBarAppearanceUpdate:

[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];

or

[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleDefault];

Note that you'll also need to set UIViewControllerBasedStatusBarAppearance to NO in the plist file if you use this method.


So I actually added a category to UINavigationController but used the methods:

-(UIViewController *)childViewControllerForStatusBarStyle;
-(UIViewController *)childViewControllerForStatusBarHidden;

and had those return the current visible UIViewController. That lets the current visible view controller set its own preferred style/visibility.

Here's a complete code snippet for it:

In Swift:

extension UINavigationController {

    public override func childViewControllerForStatusBarHidden() -> UIViewController? {
        return self.topViewController
    }

    public override func childViewControllerForStatusBarStyle() -> UIViewController? {
        return self.topViewController
    }
}

In Objective-C:

@interface UINavigationController (StatusBarStyle)

@end

@implementation UINavigationController (StatusBarStyle)

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

-(UIViewController *)childViewControllerForStatusBarHidden {
    return self.topViewController;
}

@end

And for good measure, here's how it's implemented then in a UIViewController:

In Swift

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

override func prefersStatusBarHidden() -> Bool {
    return false
}

In Objective-C

-(UIStatusBarStyle)preferredStatusBarStyle {
    return UIStatusBarStyleLightContent; // your own style
}

- (BOOL)prefersStatusBarHidden {
    return NO; // your own visibility code
}

Finally, make sure your app plist does NOT have the "View controller-based status bar appearance" set to NO. Either delete that line or set it to YES (which I believe is the default now for iOS 7?)