111

In my iPhone application built with Xcode 5 for iOS 7 I set UIViewControllerBasedStatusBarAppearance=YES in info.plist, and in my ViewController I have this code:

-(UIStatusBarStyle) preferredStatusBarStyle
{
    return UIStatusBarStyleLightContent;
}

But the status bar is still black against the black background.

I know its possible to change this app-wide by setting UIViewControllerBasedStatusBarAppearance=NO in info.plist, but I actually need to alter this on a viewController by viewController basis at runtime.

Jagat Dave
  • 1,643
  • 3
  • 23
  • 30
Andrew Smith
  • 2,919
  • 2
  • 22
  • 25
  • Hi, I have the same issue like you mentioned in question. Did you get the solution? Please provide me that. – Noundla Sandeep Feb 23 '15 at 13:29
  • You can have a look at: [Change applications status bar text color](http://stackoverflow.com/a/36324743/5299314) – 1218GG Mar 31 '16 at 05:35

19 Answers19

281

I discovered that if your ViewController is inside a navigationController then the navigationController’s navigationBar.barStyle determines the statusBarStyle.

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).

Note that this applies even if you totally change the navigationBar’s color via its barTintColor.

mxcl
  • 26,392
  • 12
  • 99
  • 98
  • 14
    I believe it's because the `UINavigationController`’s `preferredStatusBarStyle` doesn’t call through to the ViewController it hosts, and instead just returns based on its navigationBarStyle. – mxcl Oct 16 '13 at 13:51
  • In this case the view is not inside a navigation controller. – Andrew Smith Oct 17 '13 at 17:45
  • Very counterintuitive to think that bar style takes preference over an implemented method in the view controller, and only when presenting modal views! – Matej Nov 13 '14 at 21:30
  • This is the only one that worked for me and I have to say I DO find it counter intuitive. – Benjamin Feb 13 '16 at 14:35
  • 3
    `UIBarStyleBlackTranslucent ` is deprecated, use `UIBarStyleBlack` instead – Thanh-Nhon Nguyen Jul 20 '16 at 22:01
  • 2018 - navigationBar.barStyle = UIBarStyle.blackTranslucent – ricks Mar 02 '18 at 18:35
  • The `UINavigationController` is a container view controller, as mentioned by @mxcl it uses its `default preferredStatusBarStyle`. There is a `childViewControllerForStatusBarStyle` that is meant to be overridden by container view controller to provide the view controller for the preferred status bar style. In my opinion, the `UINavigationController` should provide by default the top most pushed view controller as its default implementation. Anyway, you may need to subclass (or use a category) for the nav controller to customize `childViewControllerForStatusBarStyle `. – Jaime S May 16 '18 at 16:30
  • Appears the recommended way now is to set navigation bar to... "isTranslucent = true" and "barStyle = .black". – nananta Aug 08 '18 at 21:46
  • This is a great answer pre iOS 13. However, this is now considered a "legacy customization" in iOS 13+. See my answer [below](https://stackoverflow.com/a/58544900/5739507) – Andrew Kirna Oct 24 '19 at 15:49
88

OK, here's the trick. You do have to add the key "View controller-based status bar" and set the value to No.

This is counter to what it appears the meaning of this key is, but even if you set the value to No, you can still change the appearance of the status bar, and whether it shows or not in any view controller. So it acts like "Yes" but set it to "No"!

Now I can get the status bar white or dark.

Sahil Kapoor
  • 11,183
  • 13
  • 64
  • 87
Andrew Smith
  • 2,919
  • 2
  • 22
  • 25
77

For preferredStatusBarStyle() to work within UINavigationController and UITabBarController I add the following code, which will get the preferred status bar style from the currently visible view controller.

extension UITabBarController {
    public override func childViewControllerForStatusBarStyle() -> UIViewController? {
        return selectedViewController
    }
}

extension UINavigationController {
    public override func childViewControllerForStatusBarStyle() -> UIViewController? {
        return visibleViewController
    }
}

For Swift 3 those are not methods but properties:

extension UITabBarController {
    open override var childViewControllerForStatusBarStyle: UIViewController? {
        return selectedViewController
    }
}

extension UINavigationController {
    open override var childViewControllerForStatusBarStyle: UIViewController? {
        return visibleViewController
    }
}

The Swift 4.2 properties have been renamed:

extension UITabBarController {
   open override var childForStatusBarStyle: UIViewController? {
        return selectedViewController
    }
}

extension UINavigationController {
   open override var childForStatusBarStyle: UIViewController? {
        return visibleViewController
    }
}

Usage

class ViewController: UIViewController {

    // This will be called every time the ViewController appears
    // Works great for pushing & popping
    override var preferredStatusBarStyle: UIStatusBarStyle {
        return .lightContent
    }

}
Daniel Wood
  • 4,487
  • 3
  • 38
  • 36
  • 6
    This is by far the best answer(For apps that use UINavigationController or UITabBarController – Kesava Aug 03 '16 at 12:59
  • 2
    what is the usage for this? – Anjan Biswas Oct 30 '17 at 20:48
  • @Annjawn these methods are used by UIKit. You don't need to do anything other than add it to your project. – Daniel Wood Nov 02 '17 at 10:35
  • @DanielWood yeah I figured that out and completely forgot that I used this exact same thing in one of my previous projects, although slightly differently. – Anjan Biswas Nov 02 '17 at 10:37
  • This indeed the best answer – Musa almatri Sep 22 '18 at 11:06
  • @DanielWood Bravo!! upvoted your answer and this is by far well deserved. The only problem I'm having now is when I dismiss modally presented screen (that screen had a lighter status bar and after dismissing it the new screen has default/darker status bar) it changes the status bar to light. – Arun Kumar Aug 26 '19 at 19:59
  • Note that the extension method of doing override var does not work anymore in Xcode 11.4/iOS 13.4 – Marc Etcheverry Mar 25 '20 at 02:23
  • Because extending objective C classes in Swift is implemented through Objective C categories. Overriding methods through Objective C categories is not recommended and likely to break. See https://stackoverflow.com/a/38274660/2438634 – Marc Etcheverry Mar 28 '20 at 17:37
33

I may be coming to this a bit late, but incase anyone else is looking for a working and verified app wide solution.

@mxcl is correct in describing why this is happening. In order to correct it, we simply create an extension (or category in obj-c) that overrides the preferredSatusBarStyle() method of UINavigationController. Here is an example in Swift:

extension UINavigationController {
    public override func preferredStatusBarStyle() -> UIStatusBarStyle {
        if let rootViewController = self.viewControllers.first {
            return rootViewController.preferredStatusBarStyle()
        }
        return super.preferredStatusBarStyle()
    }
}

This code simply extracts the first view controller (the root view controller) and unwraps it (in obj-c just check that it is not nil). If the unwrap is successful (not nil) then we grab the rootViewControllers preferredStatusBarStyle. Otherwise we just return the default.

Hope this helps anyone who might need it.

Cœur
  • 37,241
  • 25
  • 195
  • 267
Kyle Begeman
  • 7,169
  • 9
  • 40
  • 58
  • 2
    In Swift 2.0 You must remove "as? UIViewController" of the condition statement. – Thomás Pereira Sep 18 '15 at 21:00
  • Brilliant, I made one modification in addition to removing the "as" statement, I changed it from "first" to "last" this way whatever view controller is being seen by the user at the top of the stack will have the ability to control the color of the status bar. Awesome work, thanks for sharing! – Unome Dec 30 '15 at 21:42
  • 1
    If your navigation controller doesn't have any view controllers, this would cause an infinite loop. `return self.preferredStatusBarStyle()` would call back into this exact same method. – bearMountain Feb 04 '16 at 15:48
  • 1
    In my case, instead of using the rootViewController, I used the topViewController as during the stack the style may change. – Ric Santos May 11 '16 at 02:34
  • 2
    @Unome `visibleViewController` would be even better – Cœur Sep 02 '16 at 07:45
  • Perfect! Thats even better @Cœur – Unome Sep 02 '16 at 16:58
  • @bearMountain We are returning from super, not self. – Kyle Begeman Mar 01 '17 at 23:59
  • This is wrong and it breaks in iOS 13.4. Because extending objective C classes in Swift is implemented through Objective C categories. Overriding methods through Objective C categories is not recommended and likely to break. See https://stackoverflow.com/a/38274660/2438634 – Marc Etcheverry Mar 28 '20 at 17:36
21

To provide more detail into the accepted answer, put the following line in your app delegate's didFinishLaunchingWithOptions: method:

[UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleLightContent;

Then, in your Info.plist, add View controller-based status bar appearance and set it to NO.

I believe that's how it should be done, NOT from the navigation controller, if you want the same status bar color for the entire app. You might have screens that are not necessarily embedded in a UINavigationController, or a different UINavigationController subclass somewhere else, and other things.

EDIT: You can also do it without typing any code: https://stackoverflow.com/a/18732865/855680

Community
  • 1
  • 1
Matthew Quiros
  • 13,385
  • 12
  • 87
  • 132
10

In viewDidLoad just write this

[self setNeedsStatusBarAppearanceUpdate];

just do that and it will work

can u please try this

Set UIViewControllerBasedStatusBarAppearance to NO.
Call [[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];

One more thing i have seen in your question that you have wrote the method like this

 -(void)UIStatusBarStyle PreferredStatusBarStyle ()
        {
            return UIStatusBarStyle.LightContent;
        }

but it should be like this

-(UIStatusBarStyle)preferredStatusBarStyle{ 
    return UIStatusBarStyleLightContent; 
} 
BhavikKama
  • 8,566
  • 12
  • 94
  • 164
9

iOS 13 Solution(s)

The highest-voted answer uses "legacy" code

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 - 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

Andrew Kirna
  • 1,346
  • 16
  • 18
  • 1
    Very complete answer, thanks! Also, something I struggled for a while, on iOS 13 the `.default` style takes dark mode into consideration and it's not documented, so if you're also supporting previous iOS versions you can add `if #available(iOS 13, *) { return .darkContent } else { return .default }` if you trying to setup the status bar style manually according to the color behind the status bar and that color is "bright". – henrique Dec 07 '19 at 13:21
  • 2
    Note that the extension method of doing override var does not work anymore in Xcode 11.4/iOS 13.4 – Marc Etcheverry Mar 25 '20 at 02:22
  • Because extending objective C classes in Swift is implemented through Objective C categories. Overriding methods through Objective C categories is not recommended and likely to break. See https://stackoverflow.com/a/38274660/2438634 – Marc Etcheverry Mar 28 '20 at 17:37
  • Although overriding the UINavigationController definitely works, it looks like a bug on Apple side that it doesn't do the childForStatusBarStyle by default returning its topViewController. Eg UITabBarController does this for its tabs. To me, there's no reason why UINavigationController, being a strictly container controller for hosting "real" View controllers rather than presenting its own UI, should "eat up" those status bar stylings. Will create a radar for Apple. – Igor Vasilev Jul 07 '20 at 11:39
6

Here is how I solved it. Usually the navigationController or tabBarController are the ones deciding the appearance of the status bar (hidden, color, etc).

So I ended up subclassing the navigation controller and overriding preferredStatusBarStyle. if the current visible ViewContorller implements StatusBarStyleHandler I ask for the value to be used as the style, if it doesn't I just return a default value.

The way you trigger an update of the status bar appearance is by calling setNeedsStatusBarAppearanceUpdate which triggers preferredStatusBarStyle again and updates UI according to what the method returns

public protocol StatusBarStyleHandler {
    var preferredStatusBarStyle: UIStatusBarStyle { get }
}

public class CustomNavigationCotnroller: UINavigationController {

    public override var preferredStatusBarStyle: UIStatusBarStyle {
        if let statusBarHandler = visibleViewController as? StatusBarStyleHandler {
            return statusBarHandler.preferredStatusBarStyle
        }

        return .default
    }
}

Then usage

public class SomeController: UIViewController, StatusBarStyleHandler {

    private var statusBarToggle = true

    // just a sample for toggling the status bar style each time method is called
    private func toggleStatusBarColor() {
        statusBarToggle = !statusBarToggle
        setNeedsStatusBarAppearanceUpdate()
    }

    public override var preferredStatusBarStyle: UIStatusBarStyle {
        return statusBarToggle ? .lightContent : .default
    }
}
aryaxt
  • 76,198
  • 92
  • 293
  • 442
  • This post would be much improved if you could explain why and how this fixes the problem. –  Aug 22 '16 at 07:08
  • Instead of subclassing UINavigationController you can also just create an extension for UINavigationController and achieve the same result without having to subclass. – wilforeal Nov 02 '17 at 22:14
6

Even with all the answers here i still didn't find the exact solution for me, but started with the answer from Daniel. What I ended up with was:

override var preferredStatusBarStyle: UIStatusBarStyle {
     return visibleViewController?.preferredStatusBarStyle ?? .lightContent
}

in navigation controllers (similar for tab, just selectedViewController). And then it will respect the:

override var preferredStatusBarStyle: UIStatusBarStyle {
     return .lightContent
}

In each view controller unless you set it otherwise. I dont need to call setNeedsStatusBarAppearanceUpdate() anywhere, it just updates when you arrive at each view controller.

Andrew Plummer
  • 1,150
  • 1
  • 12
  • 21
4

1) One setting for whole project:

If available, remove UIViewControllerBasedStatusBarAppearance key-value pair from your info.plist, or set NO without removing it. If it's not available in your info.plist, do nothing. Default is NO for this property.

Add below code to your AppDelegate.m:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];
}

2) Different settings for different View Controllers:

Add UIViewControllerBasedStatusBarAppearance key-value pair to your info.plist and set it to YES.

If your View Controller is not embed in to Navigation Controller. Let's say MyViewController. just add code below to your MyViewController.m file. If your View Controller is embed in to Navigation Controller, create a new Cocoa Touch Class and make it subclass of UINavigationController. Let's say MyNC. Select Navigation Controller View on your Storyboard, at right pane; Utilities -> Identity Inspector -> Custom Class -> Class, type "MyNC". After linking Storyboard View with your "MyNC" Cocoa Touch Class, add code below to your MyNC.m:

- (BOOL)prefersStatusBarHidden {
    return NO;
}

-(UIStatusBarStyle)preferredStatusBarStyle {
    return UIStatusBarStyleLightContent;
}
Fatih Aksu
  • 3,801
  • 2
  • 21
  • 20
  • Seems in iOS9 UIViewControllerBasedStatusBarAppearance by default contains value YES, as I needed to add it manually in .plist and set to NO to work properly. – Mohd Asim Apr 27 '16 at 11:14
1

If in case you wanted to hide the statusBar during splashScreen but wanted to change the style to light content (StatusBarInitiallyHidden on Plist has to be NO to hide statusBar on splash), you can add this to appDelegate's didFinishLaunchingWithOptions method to change to lightContent.

[[UIApplication sharedApplication]setStatusBarHidden:NO withAnimation:UIStatusBarAnimationSlide];
[[UIApplication sharedApplication]setStatusBarStyle:UIStatusBarStyleLightContent];
aalesano
  • 357
  • 2
  • 13
1

swift example

in AppDelegate.swift

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
    UIApplication.sharedApplication().statusBarStyle = UIStatusBarStyle.LightContent;

    return true
}

in info.plist set View controller-based status bar appearance: NO

fyalavuz
  • 99
  • 2
  • 3
1

If you're using NavigationController, you can subclass NavigationController so that it consults its child view controller

// MyCustomNavigationController

- (NSUInteger)supportedInterfaceOrientations {
    UIViewController *viewControllerToAsk = [self findChildVC];
    return [viewControllerToAsk supportedInterfaceOrientations];
}

- (BOOL)shouldAutorotate {
    UIViewController *viewControllerToAsk = [self findChildVC];
    return [viewControllerToAsk shouldAutorotate];
}

- (UIStatusBarStyle)preferredStatusBarStyle {
    UIViewController *viewControllerToAsk = [self findChildVC];
    return [viewControllerToAsk preferredStatusBarStyle];
}

- (UIViewController *)findChildVC {
    return self.viewControllers.firstObject;
}
Cœur
  • 37,241
  • 25
  • 195
  • 267
onmyway133
  • 45,645
  • 31
  • 257
  • 263
1

Swift 4.2

extension UITabBarController {
    open override var childForStatusBarStyle: UIViewController? {
        return selectedViewController
    }
}

extension UINavigationController {
    open override var childForStatusBarStyle: UIViewController? {
        return visibleViewController
    }
}
Vyacheslav
  • 26,359
  • 19
  • 112
  • 194
  • Note that the extension method of doing override var does not work anymore in Xcode 11.4/iOS 13.4 – Marc Etcheverry Mar 25 '20 at 02:23
  • @MarcEtcheverry so, why did you downvote the answer? it seems strange. – Vyacheslav Mar 25 '20 at 06:05
  • Because extending objective C classes in Swift is implemented through Objective C categories. Overriding methods through Objective C categories is not recommended and likely to break. See https://stackoverflow.com/a/38274660/2438634 – Marc Etcheverry Mar 28 '20 at 17:35
  • @MarcEtcheverry 'not recommended' != 'never use it!'. for jul2018 the answer was correct. Even this answer is not up-to-date this is not a reason to downvote it. I can't see the future – Vyacheslav Mar 29 '20 at 12:44
0

You can set the status bar style. It will resembles the status bar like IOS 6 and below.
Paste this methods in your view controller

-(UIStatusBarStyle)preferredStatusBarStyle{
    return UIStatusBarStyleBlackOpaque;
}

and call this method from view did load like this

if([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0f)
    {
       [self setNeedsStatusBarAppearanceUpdate];
    }
Ganapathy
  • 4,594
  • 5
  • 23
  • 41
  • Do you mean `[self setStatusBarNeedsUpdate]` in the second block? (Or something else at least). – mxcl Oct 14 '13 at 16:27
0

swift example

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool { UIApplication.sharedApplication().statusBarStyle = UIStatusBarStyle.LightContent;

    return true
}

in info.plist set View controller-based status bar appearance: NO

fyalavuz
  • 99
  • 2
  • 3
0

I just want to add a note for a specific case I faced. I had another UIWindow in my app to display a chat face to be floating all over my app all the time. Doing this caused none of the solution above to work, and I am not really sure why! All what I have noticed is that my ViewController in the new UIWindow was the reason for that! And if I wanted to change the status bar style I have to do it in that view controller of the new UIWindow.

This note might help others who have a similar structure! So basically you can apply the solutions mentioned above in the ViewController of the new UIWindow.

Again this a specific case.

Thanks

Ehab Saifan
  • 290
  • 2
  • 13
0

Wanna some tricky? No needs to override status bar style in every view controller

First: Follow @Sahil Kapoor, add 'View controller-based status bar = YES' to plist

Second: Make a subclass of window's root view controller and return StatusBarTrackingController.

final class StatusBarTracker: UIViewController {
    override var preferredStatusBarStyle: UIStatusBarStyle {
        CustomThemeProvider.currentTheme.asStatusBarStyle
    }
}

final class TabBarController: UITabBarController {
     private let statusBarTracker = StatusBarTracker()

     override var childForStatusBarStyle: UIViewController? {
        statusBarTracker
    }
}

extension TabBarController: CustomThemeUpdatable {
    func applyCustomTheme(_ theme: CustomTheme) {
        setNeedsStatusBarAppearanceUpdate()
    }
}

// SceneDelegate
window?.rootViewController = TabBarController()
Eugene
  • 129
  • 1
  • 5
-1

For swift 3, in your UIViewController:

override var preferredStatusBarStyle : UIStatusBarStyle { return UIStatusBarStyle.lightContent }
laaksom
  • 2,050
  • 2
  • 18
  • 17