267

I followed this thread to override -preferredStatusBarStyle, but it isn't called. Are there any options that I can change to enable it? (I'm using XIBs in my project.)

Community
  • 1
  • 1
trgoofi
  • 2,931
  • 3
  • 13
  • 11

25 Answers25

1045

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;
shim
  • 7,170
  • 10
  • 62
  • 95
Tyson
  • 14,406
  • 6
  • 26
  • 40
  • 60
    Nice! Note that `preferredStatusBarStyle` will in fact get called on the child view controller if you hide the navigation bar (set `navigationBarHidden` to `YES`), exactly as appropriate. – Patrick Pijnappel Dec 16 '13 at 10:08
  • 25
    Thanks for this answer. If you want to set the barStyle for all your navigation bars, call `[[UINavigationBar appearance] setBarStyle:UIBarStyleBlack]` – Thomas Desert Jan 23 '14 at 09:59
  • 15
    Perfect answer. None of the other answers on SO took the UINavigationController into consideration. 2 hours of banging my head against the keyboard. – Ryan Alford Feb 22 '14 at 16:49
  • 3
    Great tip and you can do it from Storyboard too. – Ben Affleck Apr 30 '14 at 19:16
  • This also has the added benefit of removing the pixel shadow at the bottom of the navigationBar it would appear. – Daniel Wood Jun 24 '14 at 09:43
  • Note, this will change the color for all views using the navigation controller, so you need to set it back to UIBarStyleDefault when necessary. I did it by making the call on the Back button events. – Arbie Samong Jul 09 '14 at 06:29
  • If you use this approach, you end up changing the navigation bar color in addition to the status bar. If you just want the status bar, this won't work, afaict. – Janene Pappas Jul 11 '14 at 20:23
  • 11
    Kudos to @Patrick for indicating that `navigationBarHidden` set to `YES` will actually have `preferredStatusBarStyle` called, and a warning to those that might stumble on this: it works with `navigationBarHidden`, but not with `navigationBar.hidden`! – jcaron Dec 04 '14 at 17:59
  • [[UINavigationBar appearance] setBarStyle:UIBarStyleBlack]; – Mazen Kasser Aug 06 '15 at 08:57
  • 5
    should be obvious, but you also need "View controller-based status bar appearance" set to YES in Info.plist for this to work. – Code Baller Oct 01 '15 at 05:37
  • 1
    If I set the bar style to `UIBarStyleBlack `, the var tint color turns a very dark gray. But I want to keep my bar tint color (in this case, a dark blue - that's why I need light content). – Nicolas Miari Oct 23 '15 at 04:59
  • 1
    I followed this but id doesn't work. Even if I added View controller-based status bar appearance" set to YES in info.plist. However, the extension from @serenn worked perfectly – KML Oct 25 '15 at 19:12
  • Good answer! Thanks. – Long Pham Jan 06 '16 at 10:18
  • I think this should considered as the correct answer! Saved my life! – Kai Burghardt Apr 19 '16 at 08:30
  • If you have nested VC's in a navigation controller, you have to do it this way. Also make sure your View Controller preffered setting in your info.plist is set to YES. – cloudcal Dec 23 '16 at 18:57
  • 2
    For Swift 3 you do `self.navigationController?.navigationBar.barStyle = .black` – jonnysamps Jan 20 '17 at 00:53
  • 2
    THANK YOU. Holy moly, iOS is the pits at times. If it wasn't for the cheery contributors of Stack Overflow, I'd have ditched this platform a long time ago. – Womble Feb 09 '17 at 01:09
  • I can't seem to get this to work on iOS 10. Does any one get the same problem? – user1615898 Jun 24 '17 at 19:17
  • 1
    Agree to @CodeBaller set `View controller-based status bar appearance` to `YES` in your Info.plist – guozqzzu Sep 29 '17 at 04:37
  • Using this implementation will apply it for every VC after calling this just like @ArbieSamong said. However using .default to put it back to black icons does not work... – Tom Kraak Jun 22 '20 at 07:14
  • Correct solution if you are using a dark backgrounds for your navigation bar. – Peter Suwara Jan 26 '21 at 06:04
  • Thanks, you saved my hair today bro @Tyson – jacob Apr 19 '21 at 09:30
121

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.

BergQuester
  • 6,099
  • 24
  • 38
AbdullahC
  • 6,420
  • 3
  • 24
  • 42
  • 2
    I have the same problem as you, not setting the root view controller. How the hell did you find that? – trgoofi Sep 27 '13 at 01:26
  • 1
    I suspected that something in the framework wasn't getting the notification from `setNeedsStatusBarAppearanceUpdate` - my suspicions were confirmed when I made this change. – AbdullahC Sep 27 '13 at 02:09
  • 2
    A related issue that I found in an app was a view controller with a full screen child view controller that did not override childViewControllerForStatusBarStyle and childViewControllerForStatusBarHidden to return that child view controller. If you have your own view controller hierarchy you need to provide these methods to inform the system of which view controller should be used to determine the status bar style. – Jon Steinmetz Oct 29 '13 at 16:19
  • setting the rootviewcontroller does not change anything. You should work with the comment of Jon. And be careful when calling setneedsstatusbarappearanceUpdate. You should call it from the parent to work. – doozMen Aug 28 '14 at 13:08
  • 1
    @Hippo you are genius!! How did you find that it was because of not setting rootviewcontroller? – ViruMax Nov 07 '14 at 07:19
  • and what about to hide status bar in `rootViewController` and show in all other child view with `UIStatusBarStyleLightContent` – Subhash Sharma Oct 19 '15 at 10:08
  • This is an old issue but for those among us stumbling upon that issue nowadays I would recommend using `modalPresentationCapturesStatusBarAppearance` for any non full screen modal transition. This helped be with an issue on iOS8 and a custom transition. – MiKL Nov 11 '15 at 12:11
  • Be aware that this alternative solution is deprecated since iOS 9.0 ([[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];) – Michał W. Jun 24 '16 at 14:37
  • this is deprecated in ios 9 – Zakaria Darwish Aug 01 '16 at 09:08
102

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

Anton Tropashko
  • 4,300
  • 3
  • 31
  • 53
serenn
  • 1,784
  • 1
  • 14
  • 11
  • Looks like `return self.topViewController;` works for me, but `return self.visibleViewController;` - not – k06a Nov 21 '14 at 10:37
  • visibleViewController can return currently presented modal controller when you dismiss it. Which is bummer. Use topViewController. – Ben Affleck Mar 09 '15 at 11:47
  • This is the best answer I've seen on the matter so far, and the only answer where the writer is not afraid to set the "View controller-based status bar appearance" to YES (which is the default). Well done. – csotiriou Jul 28 '15 at 08:08
  • Overriding framework methods in a category is really bad idea. – d.lebedev Jun 05 '16 at 20:12
  • @EdwardSammutAlessi http://stackoverflow.com/questions/5272451/overriding-methods-using-categories-in-objective-c this topic explains it pretty well – d.lebedev Jun 08 '16 at 09:32
  • 1
    @d.lebedev ok, but I don't think any of those problems apply here. You don't need to call `super` in this method and you do actually want to change the behaviour of all controllers of this type – ed' Jun 08 '16 at 09:44
  • 1
    this isn't working for me on iOS 9.3. I guess, this is the problem: *This issue is of particular significance because many of the Cocoa classes are implemented using categories. A framework-defined method you try to override may itself have been implemented in a category, and so which implementation takes precedence is not defined.* – vikingosegundo Sep 20 '16 at 21:02
  • @serenn How does it for iOS10, swift3? – aircraft Dec 06 '16 at 14:52
  • 2
    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:40
  • @MarcEtcheverry this particular instance wasn't wrong. The fact of the matter is that the subclasses of other objects/protocols such as UINavigationController had no prior implementation of these to conflict in dynamic dispatch. There were no defaults or implementations within the actual subclasses, which is why this was the cleanest way to implement this across an app without creating an unnecessary dependency (period). Unfortunately, 13.4 seems to have changed this behavior. I'm guessing behind the scenes they have a check or implementation now which has been non-existent for years......... – TheCodingArt Apr 01 '20 at 17:00
  • Note, that with the current change, honestly, Apple themselves should provide these interfaces though Protocols that swift can provide default values for based on types through a protocol extension. The current idea of providing a subclassed shared dependency app wide is fairly absurd, especially in large apps with independent feature/flow/view implementations. – TheCodingArt Apr 01 '20 at 17:06
83

For anyone still struggling with this, this simple extension in swift should fix the problem for you.

extension UINavigationController {
    override open var childForStatusBarStyle: UIViewController? {
        return self.topViewController
    }
}
John
  • 13
  • 2
Alex Brown
  • 1,553
  • 1
  • 11
  • 22
  • 12
    You sir deserve a medal. – nikans Jul 29 '17 at 01:59
  • 2
    Thank you very much man. I was returning visibleViewController instead with no success. – Fábio Salata Aug 23 '17 at 23:49
  • I put this inside my view controller that GOT pushed, it doesn't get called. – mskw Jan 23 '18 at 17:51
  • 1
    This is gold. I have a navigation controller embedded in a tab bar and I just threw this in a file and now I can change status bar appearance anywhere I want. – Vahid Amiri May 23 '18 at 20:36
  • 2
    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:38
  • 1
    @MarcEtcheverry this particular instance wasn't wrong. The fact of the matter is that the subclasses of other objects/protocols such as UINavigationController had no prior implementation of these to conflict in dynamic dispatch. There were no defaults or implementations within the actual subclasses, which is why this was the cleanest way to implement this across an app without creating an unnecessary dependency (period). Unfortunately, 13.4 seems to have changed this behavior. I'm guessing behind the scenes they have a check or implementation now which has been non-existent for years......... – TheCodingArt Apr 01 '20 at 17:00
22

My app used all three: UINavigationController, UISplitViewController, UITabBarController, thus these all seem to take control over the status bar and will cause preferedStatusBarStyle to not be called for their children. To override this behavior you can create an extension like the rest of the answers have mentioned. Here is an extension for all three, in Swift 4. Wish Apple was more clear about this sort of stuff.

extension UINavigationController {
    open override var childViewControllerForStatusBarStyle: UIViewController? {
        return self.topViewController
    }

    open override var childViewControllerForStatusBarHidden: UIViewController? {
        return self.topViewController
    }
}

extension UITabBarController {
    open override var childViewControllerForStatusBarStyle: UIViewController? {
        return self.childViewControllers.first
    }

    open override var childViewControllerForStatusBarHidden: UIViewController? {
        return self.childViewControllers.first
    }
}

extension UISplitViewController {
    open override var childViewControllerForStatusBarStyle: UIViewController? {
        return self.childViewControllers.first
    }

    open override var childViewControllerForStatusBarHidden: UIViewController? {
        return self.childViewControllers.first
    }
}

Edit: Update for Swift 4.2 API changes

extension UINavigationController {
    open override var childForStatusBarStyle: UIViewController? {
        return self.topViewController
    }

    open override var childForStatusBarHidden: UIViewController? {
        return self.topViewController
    }
}

extension UITabBarController {
    open override var childForStatusBarStyle: UIViewController? {
        return self.children.first
    }

    open override var childForStatusBarHidden: UIViewController? {
        return self.children.first
    }
}

extension UISplitViewController {
    open override var childForStatusBarStyle: UIViewController? {
        return self.children.first
    }

    open override var childForStatusBarHidden: UIViewController? {
        return self.children.first
    }
}
Luis
  • 876
  • 12
  • 27
  • 1
    This is the only solution that works. All answers on SO point to the standard solution which won't work for any app with NavigationControllers. Thank you!!! – Houman Aug 18 '18 at 18:55
  • Using extensions for overriding is just wrong. That's not safe. There are multiple simpler solutions. Use a subclass instead. – Sulthan Mar 28 '20 at 16:53
  • 2
    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:39
  • 1
    @MarcEtcheverry this particular instance wasn't wrong. The fact of the matter is that the subclasses of other objects/protocols such as UINavigationController had no prior implementation of these to conflict in dynamic dispatch. There were no defaults or implementations within the actual subclasses, which is why this was the cleanest way to implement this across an app without creating an unnecessary dependency (period). Unfortunately, 13.4 seems to have changed this behavior. I'm guessing behind the scenes they have a check or implementation now which has been non-existent for years......... – TheCodingArt Apr 01 '20 at 17:00
17

On a UINavigationController, preferredStatusBarStyle is not called because its topViewController is preferred to self. So, to get preferredStatusBarStyle called on an UINavigationController, you need to change its childViewControllerForStatusBarStyle.

Recommendation

Override your UINavigationController in your class:

class MyRootNavigationController: UINavigationController {
    override var preferredStatusBarStyle: UIStatusBarStyle {
        return .lightContent
    }
    override var childViewControllerForStatusBarStyle: UIViewController? {
        return nil
    }
}

Non recommended alternative

To do it for all UINavigationController, you could override in an extension (warning: it affects UIDocumentPickerViewController, UIImagePickerController, etc.), but you should probably not do it according to Swift documentation:

extension UINavigationController {
    open override var preferredStatusBarStyle: UIStatusBarStyle {
        return .lightContent
    }
    open override var childViewControllerForStatusBarStyle: UIViewController? {
        return nil
    }
}
Cœur
  • 32,421
  • 21
  • 173
  • 232
15

Tyson's answer is correct for changing the status bar color to white in UINavigationController.

If anyone want's to accomplish the same result by writing the code in AppDelegate then use below code and write it inside AppDelegate's didFinishLaunchingWithOptions method.

And don't forget to set the UIViewControllerBasedStatusBarAppearance to YES in the .plist file, else the change will not reflect.

Code

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
     // status bar appearance code
     [[UINavigationBar appearance] setBarStyle:UIBarStyleBlack];

     return YES;
}
Community
  • 1
  • 1
Yogesh Suthar
  • 29,554
  • 17
  • 66
  • 96
13

In addition to serenn's answer, if you are presenting a view controller with a modalPresentationStyle (for example .overCurrentContext), you should also call this on the newly presented view controller:

presentedViewController.modalPresentationCapturesStatusBarAppearance = true

Don't forget to also override the preferredStatusBarStyle in the presented view controller.

frin
  • 4,243
  • 3
  • 26
  • 23
12

Swift 4.2 and above

As mentioned in selected answer, root cause is to check your window root view controller object.

Possible cases of your flow structure

  • Custom UIViewController object is window root view controller

    Your window root view controller is a UIViewController object and it further adds or removes navigation controller or tabController based on your application flow.

    This kind of flow is usually used if your app has pre login flow on navigation stack without tabs and post login flow with tabs and possibly every tab further holds navigation controller.

  • TabBarController object is window root view controller

    This is the flow where window root view controller is tabBarController possibly every tab further holds navigation controller.

  • NavigationController object is window root view controller

    This is the flow where window root view controller is navigationController.

    I am not sure if there is any possibility to add tab bar controller or new navigation controller in an existing navigation controller. But if there is such case, we need to pass the status bar style control to the next container. So, I added the same check in UINavigationController extension to find childForStatusBarStyle

Use following extensions, it handles all above scenarios -

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

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

extension AppRootViewController {
    open override var preferredStatusBarStyle: UIStatusBarStyle {
        return children.first { $0.childForStatusBarStyle != nil }?.childForStatusBarStyle?.preferredStatusBarStyle ?? .default
    }
}
  • You don't need UIViewControllerBasedStatusBarAppearance key in info.plist as it true by default

Points to consider for more complex flows

  • In case you present new flow modally, it detaches from the existing status bar style flow. So, suppose you are presenting a NewFlowUIViewController and then add new navigation or tabBar controller to NewFlowUIViewController, then add extension of NewFlowUIViewController as well to manage further view controller's status bar style.

  • In case you set modalPresentationStyle other than fullScreen while presenting modally, you must set modalPresentationCapturesStatusBarAppearance to true so that presented view controller must receive status bar appearance control.

abhimanyu jindal
  • 573
  • 7
  • 16
  • Excellent answer! – Amin Benarieb Jul 24 '19 at 09:12
  • 3
    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:38
  • @MarcEtcheverry this particular instance wasn't wrong. The fact of the matter is that the subclasses of other objects/protocols such as UINavigationController had no prior implementation of these to conflict in dynamic dispatch. There were no defaults or implementations within the actual subclasses, which is why this was the cleanest way to implement this across an app without creating an unnecessary dependency (period). Unfortunately, 13.4 seems to have changed this behavior. I'm guessing behind the scenes they have a check or implementation now which has been non-existent for years......... – TheCodingArt Apr 01 '20 at 17:01
  • the UINavigationController extension did the trick for me! Thanks! – Sergio Bernal Aug 14 '20 at 20:32
9

An addition to Hippo's answer: if you're using a UINavigationController, then it's probably better to add a category:

//  UINavigationController+StatusBarStyle.h:

@interface UINavigationController (StatusBarStyle)

@end



//  UINavigationController+StatusBarStyle.m:

@implementation UINavigationController (StatusBarStyle)

- (UIStatusBarStyle)preferredStatusBarStyle
{
    //also you may add any fancy condition-based code here
    return UIStatusBarStyleLightContent;
}

@end

That solution is probably better than switching to soon-to-be deprecated behaviour.

Artem Abramov
  • 4,536
  • 3
  • 23
  • 37
  • Don't do this, it works for now but may break future behavior. Just change the navBar style - see my answer http://stackoverflow.com/a/19513714/505457 – Tyson Oct 24 '13 at 23:02
  • 2
    You should use subclass,not category. – shuiyouren Nov 21 '13 at 10:34
  • 2Tyson: Why will it break future behaviour? preferredStatusBarStyle: is the Apple's preferred method to set up Status Bar style. – Artem Abramov Nov 22 '13 at 16:15
  • 2shuiyouren: Why should I increase complexity by subclassing if I can just use a category and include it in every place where I will to? Anyway, that's a question of architecture, not the implementation. – Artem Abramov Nov 22 '13 at 16:17
  • 2
    @ArtemAbramov Because the UINavigationController already implements `preferredStatusBarStyle` and does UINavigationController specific logic. Right now this logic is based on `navigationBar.barStyle` but I can see additional checks being added (e.g. `UISearchDisplayController` moving to hide navbar mode). By overriding the default logic you loose all this functionality and leave yourself open for annoying 'wtf' moments in the future. See my answer above for the correct way to do this while still supporting in-built nav controller behaviour. – Tyson Nov 27 '13 at 03:37
9

iOS 13 Solution(s)

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. Opt out of Dark Mode entirely
    • In your info.plist, add the following property:
      • Key - UIUserInterfaceStyle (aka. "User Interface Style")
      • Value - Light
  2. 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
          }
      }
      
  3. 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
  • 986
  • 11
  • 16
  • My status bar is getting black after screen rotation. Any idea why? This only happens on iPad Pro simulator. – Pedro Paulo Amorim Jan 17 '20 at 14:58
  • @PedroPauloAmorim, can you provide more info? How is the top view controller presented (modal, full screen, show)? Is it nested inside a navigation controller? Is the text turning black, or the background too? What are you trying to accomplish? – Andrew Kirna Jan 22 '20 at 18:23
  • I set the light status bar in my whole app. It gets light in two rotations, in the third one it turns dark and never return to light, even forcing to redraw it. It's happening on iPad Pro simulator. The views are getting presented in fullscreen and they are not nested inside a navigation controller. Only the text turns dark. – Pedro Paulo Amorim Jan 22 '20 at 18:37
  • How are you setting the light status bar in the first place? – Andrew Kirna Jan 29 '20 at 06:09
  • I mean, what did you do to implement its current state? (Edit the .plist? Override the preferredStatusBarStyle in each nav controller/view controller? Override childForStatusBarStyle in each nav controller?) – Andrew Kirna Jan 29 '20 at 16:15
  • Override preferredStatusBarStyle. I also `overrideUserInterfaceStyle` when the app is starting only once. I put a breakpoint on all `preferredStatusBarStyle` and my app is not calling the black one, instead the system does. – Pedro Paulo Amorim Jan 29 '20 at 18:38
  • You shouldn't explicitly access `preferredStatusBarStyle`. As long as you override it, the system should access it. I don't think `overrideUserInterfaceStyle` should affect the other setting but the main problem is it acts differently on iOS vs. iPadOS. I haven't debugged iPads much. – Andrew Kirna Jan 29 '20 at 19:35
  • I am not accessing `preferredStatusBarStyle` instead overriding it as per the documentation. – Pedro Paulo Amorim Jan 30 '20 at 12:15
  • 3
    Your overriding via extension is not a real override. It's an unsafe misusing of the language. That can break very easily. – Sulthan Mar 28 '20 at 16:50
  • @Sulthan, thanks for pointing that out. Someone else recently mentioned that the extension overrides are no longer valid in iOS 13.4. Do you have any documentation for this? – Andrew Kirna Apr 14 '20 at 15:54
  • I chose an implementation of `childForStatusBarStyle` inspecting `visibleViewController` to handle the modally presented vcs. But you probably don't want to return `visibleViewController` in the following cases: **1)** `vc.isBeingPresented && vc.modalPresentationStyle != .fullScreen && vc.modalPresentationCapturesStatusBarAppearance == false` re https://developer.apple.com/documentation/uikit/uiviewcontroller/1621453-modalpresentationcapturesstatusb **and 2)** `vc.isBeingDismissed` – Romano Mar 19 '21 at 13:51
7

@serenn's answer above is still a great one for the case of UINavigationControllers. However, for swift 3 the childViewController functions have been changed to vars. So the UINavigationController extension code should be:

override open var childViewControllerForStatusBarStyle: UIViewController? {
  return topViewController
}

override open var childViewControllerForStatusBarHidden: UIViewController? {
  return topViewController
}

And then in the view controller that should dictate the status bar style:

override var preferredStatusBarStyle: UIStatusBarStyle {
   return .lightContent
}
Community
  • 1
  • 1
John Stricker
  • 383
  • 3
  • 6
  • 2
    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:39
  • @MarcEtcheverry this particular instance wasn't wrong. The fact of the matter is that the subclasses of other objects/protocols such as UINavigationController had no prior implementation of these to conflict in dynamic dispatch. There were no defaults or implementations within the actual subclasses, which is why this was the cleanest way to implement this across an app without creating an unnecessary dependency (period). Unfortunately, 13.4 seems to have changed this behavior. I'm guessing behind the scenes they have a check or implementation now which has been non-existent for years......... – TheCodingArt Apr 01 '20 at 17:01
6

If your viewController is under UINavigationController.

Subclass UINavigationController and add

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

ViewController's preferredStatusBarStyle will be called.

PowHu
  • 2,061
  • 14
  • 17
4

UIStatusBarStyle in iOS 7

The status bar in iOS 7 is transparent, the view behind it shows through.

The style of the status bar refers to the appearances of its content. In iOS 7, the status bar content is either dark (UIStatusBarStyleDefault) or light (UIStatusBarStyleLightContent). Both UIStatusBarStyleBlackTranslucent and UIStatusBarStyleBlackOpaque are deprecated in iOS 7.0. Use UIStatusBarStyleLightContent instead.

How to change UIStatusBarStyle

If below the status bar is a navigation bar, the status bar style will be adjusted to match the navigation bar style (UINavigationBar.barStyle):

Specifically, if the navigation bar style is UIBarStyleDefault, the status bar style will be UIStatusBarStyleDefault; if the navigation bar style is UIBarStyleBlack, the status bar style will be UIStatusBarStyleLightContent.

If there is no navigation bar below the status bar, the status bar style can be controlled and changed by an individual view controller while the app runs.

-[UIViewController preferredStatusBarStyle] is a new method added in iOS 7. It can be overridden to return the preferred status bar style:

- (UIStatusBarStyle)preferredStatusBarStyle
  {
      return UIStatusBarStyleLightContent;
  }

If the status bar style should be controlled by a child view controller instead of self, override -[UIViewController childViewControllerForStatusBarStyle] to return that child view controller.

If you prefer to opt out of this behavior and set the status bar style by using the -[UIApplication statusBarStyle] method, add the UIViewControllerBasedStatusBarAppearance key to an app’s Info.plist file and give it the value NO.

oscarr
  • 453
  • 5
  • 5
3

If anyone is using a Navigation Controller and wants all of their navigation controllers to have the black style, you can write an extension to UINavigationController like this in Swift 3 and it will apply to all navigation controllers (instead of assigning it to one controller at a time).

extension UINavigationController {

    override open func viewDidLoad() {
        super.viewDidLoad()

        self.navigationBar.barStyle = UIBarStyle.black
    }

}
Benjamin Lowry
  • 3,392
  • 1
  • 20
  • 25
3

In my case, I've accidentally presented the View/Navigation Controller as UIModalPresentationStyle.overFullScreen, which causes preferredStatusBarStyle not being called. After switching it back to UIModalPresentationStyle.fullScreen, everything works.

Hasta Dhana
  • 4,631
  • 7
  • 17
  • 26
Casey
  • 96
  • 3
  • this was totally what solved my problem. I had set my modalPresentationStyle = .custom and that was the problem. – horseshoe7 Oct 09 '20 at 19:10
  • If you still want a `overFullScreen` presentation style, you have to add in your `viewDidLoad` : `modalPresentationCapturesStatusBarAppearance = true` – Ded77 Mar 26 '21 at 13:10
2

As for iOS 13.4 the preferredStatusBarStyle method in UINavigationController category will not be called, swizzling seems to be the only option without the need of using a subclass.

Example:

Category header:

@interface UINavigationController (StatusBarStyle)
+ (void)setUseLightStatusBarStyle;
@end

Implementation:

#import "UINavigationController+StatusBarStyle.h"
#import <objc/runtime.h>

@implementation UINavigationController (StatusBarStyle)

void (^swizzle)(Class, SEL, SEL) = ^(Class c, SEL orig, SEL new){
    Method origMethod = class_getInstanceMethod(c, orig);
    Method newMethod = class_getInstanceMethod(c, new);
    if(class_addMethod(c, orig, method_getImplementation(newMethod), method_getTypeEncoding(newMethod)))
        class_replaceMethod(c, new, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
    else
        method_exchangeImplementations(origMethod, newMethod);
};

+ (void)setUseLightStatusBarStyle {
    swizzle(self.class, @selector(preferredStatusBarStyle), @selector(_light_preferredStatusBarStyle));
}

- (UIStatusBarStyle)_light_preferredStatusBarStyle {
    return UIStatusBarStyleLightContent;
}    
@end

Usage in AppDelegate.h:

#import "UINavigationController+StatusBarStyle.h"

[UINavigationController setUseLightStatusBarStyle];
Kevin Flachsmann
  • 376
  • 1
  • 3
  • 16
  • this is one of the great solution. spend too much time on it but was not working any solution even after creation of Category of UINavigationController. but this working fine. – Hitarth Mar 24 '21 at 11:46
1

In Swift for any kind of UIViewController:

In your AppDelegate set:

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    window!.rootViewController = myRootController
    return true
}

myRootController can be any kind of UIViewController, e.g. UITabBarController or UINavigationController.

Then, override this root controller like this:

class RootController: UIViewController {
    override func preferredStatusBarStyle() -> UIStatusBarStyle {
        return .LightContent
    }
}

This will change the appearance of the status bar in your whole app, because the root controller is solely responsible for the status bar appearance.

Remember to set the property View controller-based status bar appearance to YES in your Info.plist to make this work (which is the default).

Damnum
  • 1,729
  • 15
  • 35
1

Swift 3 iOS 10 Solution:

override var preferredStatusBarStyle: UIStatusBarStyle {
    return .lightContent
 }
Statik
  • 312
  • 1
  • 2
  • 14
1

Most of the answers don't include good implementation of childViewControllerForStatusBarStyle method for UINavigationController. According to my experience you should handle such cases as when transparent view controller is presented over navigation controller. In these cases you should pass control to your modal controller (visibleViewController), but not when it's disappearing.

override var childViewControllerForStatusBarStyle: UIViewController? {
  var childViewController = visibleViewController
  if let controller = childViewController, controller.isBeingDismissed {
    childViewController = topViewController
  }
  return childViewController?.childViewControllerForStatusBarStyle ?? childViewController
}
Timur Bernikovich
  • 4,733
  • 3
  • 39
  • 53
0

Here's my method for solving this.

Define a protocol called AGViewControllerAppearance.

AGViewControllerAppearance.h

#import <Foundation/Foundation.h>

@protocol AGViewControllerAppearance <NSObject>

@optional

- (BOOL)showsStatusBar;
- (BOOL)animatesStatusBarVisibility;
- (UIStatusBarStyle)preferredStatusBarStyle;
- (UIStatusBarAnimation)prefferedStatusBarAnimation;

@end

Define a category on UIViewController called Upgrade.

UIViewController+Upgrade.h

#import <UIKit/UIKit.h>

@interface UIViewController (Upgrade)

//
//  Replacements
//

- (void)upgradedViewWillAppear:(BOOL)animated;

@end

UIViewController+Upgrade.m

#import "UIViewController+Upgrade.h"

#import <objc/runtime.h>

#import "AGViewControllerAppearance.h" // This is the appearance protocol

@implementation UIViewController (Upgrade)

+ (void)load
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wselector"
    Method viewWillAppear = class_getInstanceMethod(self, @selector(viewWillAppear:));
#pragma clang diagnostic pop
    Method upgradedViewWillAppear = class_getInstanceMethod(self, @selector(upgradedViewWillAppear:));
    method_exchangeImplementations(viewWillAppear, upgradedViewWillAppear);
}

#pragma mark - Implementation

- (void)upgradedViewWillAppear:(BOOL)animated
{
    //
    //  Call the original message (it may be a little confusing that we're
    //  calling the 'same' method, but we're actually calling the original one :) )
    //

    [self upgradedViewWillAppear:animated];

    //
    //  Implementation
    //

    if ([self conformsToProtocol:@protocol(AGViewControllerAppearance)])
    {
        UIViewController <AGViewControllerAppearance> *viewControllerConformingToAppearance =
        (UIViewController <AGViewControllerAppearance> *)self;

        //
        //  Status bar
        //

        if ([viewControllerConformingToAppearance respondsToSelector:@selector(preferredStatusBarStyle)])
        {
            BOOL shouldAnimate = YES;

            if ([viewControllerConformingToAppearance respondsToSelector:@selector(animatesStatusBarVisibility)])
            {
                shouldAnimate = [viewControllerConformingToAppearance animatesStatusBarVisibility];
            }

            [[UIApplication sharedApplication] setStatusBarStyle:[viewControllerConformingToAppearance preferredStatusBarStyle]
                                                        animated:shouldAnimate];
        }

        if ([viewControllerConformingToAppearance respondsToSelector:@selector(showsStatusBar)])
        {
            UIStatusBarAnimation animation = UIStatusBarAnimationSlide;

            if ([viewControllerConformingToAppearance respondsToSelector:@selector(prefferedStatusBarAnimation)])
            {
                animation = [viewControllerConformingToAppearance prefferedStatusBarAnimation];
            }

            [[UIApplication sharedApplication] setStatusBarHidden:(! [viewControllerConformingToAppearance showsStatusBar])
                                                    withAnimation:animation];
        }
    }
}

@end

Now, it's time to say that you're view controller is implementing the AGViewControllerAppearance protocol.

Example:

@interface XYSampleViewController () <AGViewControllerAppearance>

... the rest of the interface

@end

Of course, you can implement the rest of the methods (showsStatusBar, animatesStatusBarVisibility, prefferedStatusBarAnimation) from the protocol and UIViewController+Upgrade will do the proper customization based on the values provided by them.

arturgrigor
  • 8,811
  • 4
  • 28
  • 31
0

If someone run into this problem with UISearchController. Just create a new subclass of UISearchController, and then add code below into that class:

override func preferredStatusBarStyle() -> UIStatusBarStyle {
    return .LightContent
}
josliber
  • 41,865
  • 12
  • 88
  • 126
Tai Le
  • 6,003
  • 2
  • 35
  • 26
0

Note that when using the self.navigationController.navigationBar.barStyle = UIBarStyleBlack; solution

be sure to go to your plist and set "View controller-based status bar appearance" to YES. If its NO it will not work.

Richard Garfield
  • 377
  • 3
  • 10
  • Setting UIViewControllerBasedStatusBarAppearance to YES in the project plist made all the difference for me. I had forgotten about it. – filo May 09 '18 at 10:50
0

Since Xcode 11.4, overriding the preferredStatusBarStyle property in a UINavigationController extension no longer works since it will not be called.

Setting the barStyle of navigationBar to .black works indeed but this will add unwanted side effects if you add subviews to the navigationBar which may have different appearances for light and dark mode. Because by setting the barStyle to black, the userInterfaceStyle of a view thats embedded in the navigationBar will then always have userInterfaceStyle.dark regardless of the userInterfaceStyle of the app.

The proper solution I come up with is by adding a subclass of UINavigationController and override preferredStatusBarStyle there. If you then use this custom UINavigationController for all your views you will be on the save side.

PatrickDotStar
  • 1,397
  • 1
  • 14
  • 18
-1

The NavigationController or TabBarController are the ones that need to provide the style. Here is how I solved: https://stackoverflow.com/a/39072526/242769

Community
  • 1
  • 1
aryaxt
  • 69,636
  • 87
  • 281
  • 421
  • If you think that this is a duplicate of another question, please close vote it as duplicate –  Aug 22 '16 at 07:07