28

My navigation bar has a white backgroundColor and my status bar uses the dark textColor. When a user changes the iOS theme to Dark Mode, the status bar changes to white text on a white background. As a result, I can't see anything. How can I disable this change for my app?

Andrew Kirna
  • 1,346
  • 16
  • 18
EvGeniy Ilyin
  • 1,817
  • 1
  • 21
  • 38
  • It sounds like you don't wish to support darkMode at all. You can opt out of supporting darkMode. That's been covered here. – rmaddy Aug 15 '19 at 23:35
  • yes You right, but > here < - where? I do not see link – EvGeniy Ilyin Aug 22 '19 at 10:41
  • 1
    I meant Stack Overflow. Search on opting out of dark mode. – rmaddy Aug 22 '19 at 17:58
  • > [here](https://stackoverflow.com/questions/56537855/is-it-possible-to-opt-out-of-dark-mode-on-ios-13) < and > [here](https://stackoverflow.com/questions/56546267/ios-13-disable-dark-mode-changes) – DoesData Aug 30 '19 at 14:34
  • 1
    Even if you implement the suggestions in those posts you will likely still have problems with the status bar unless you override its default behaviour already in your app. – shim Oct 11 '19 at 15:19

9 Answers9

47

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.

Edits: Strikethrough edits were made to remove extensions as a suggested answer. Other developers noted that they stopped working in Xcode 11.4 and Apple's documentation discourages the use of this ambiguous behavior.

P.S. My code uses Swift 5.1 syntax

Andrew Kirna
  • 1,346
  • 16
  • 18
  • 1
    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:21
  • @MarcEtcheverry do you know what the alternative is for 13.4? – alextudge Apr 06 '20 at 15:43
  • Subclass any of those container view controllers and override there, which is the proper way to do this. Swift extensions for ObjC classes may be implemented using ObjC categories, which never could safely override things. You could swizzle it in ObjC, but subclassing is safer. – Marc Etcheverry Apr 07 '20 at 19:48
  • I'm using Xcode 11.4 and right now, I have view controller presented on a custom UINavigationController and others that are presented on a native UINavigationController. childForStatusBarStyle is only called for the view controllers that are presented on the native UINavigationController. This is so annoying. Does anyone have any idea why ? – Mohamed Aziz Bessrour Apr 09 '20 at 21:59
  • @MarcEtcheverry, thanks for pointing that out. Do you have any documentation for this so I can update my answer? – Andrew Kirna Apr 14 '20 at 15:59
  • 1
    "If the name of a method declared in a category is the same as a method in the original class, or a method in another category on the same class (or even a superclass), the behavior is undefined as to which method implementation is used at runtime. This is less likely to be an issue if you’re using categories with your own classes, but can cause problems when using categories to add methods to standard Cocoa or Cocoa Touch classes". https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.htm – Marc Etcheverry Apr 15 '20 at 08:08
  • You can follow all of links and answers here: https://stackoverflow.com/a/38274660/2438634 I think the issue lies in that people forget that extensions on Objective C objects will end up being implemented as ObjC categories, which have this problem. So, forget about overriding, and all extensions members should be prefixed as well unless they are @nonobjc. – Marc Etcheverry Apr 15 '20 at 08:14
  • subclassing UINavigationController is the only thing that worked for me because my **info.plist** has **View controller-based status bar appearance = YES** – Lance Samaria Jun 26 '20 at 05:06
  • 1
    I worked for Darkmode (have to make it same as light mode as per Client requirement) then after finished my work just saw this that we can exclude support for darkmode just with a keyword! :D good for next time atleast..! – Ammar Mujeeb Aug 17 '20 at 10:24
17

If you set the UIViewControllerBasedStatusBarAppearance key in your app's info.plist to YES, you can override the status bar style in your currently presented view controller:

override var preferredStatusBarStyle: UIStatusBarStyle {
    if #available(iOS 13, *) {
        return .darkContent
    } else {
        return .default
    }
}

and call the setNeedsStatusBarAppearanceUpdate() method

Awais Mobeen
  • 733
  • 11
  • 19
Frank Rupprecht
  • 9,191
  • 31
  • 56
  • I am using both of those configurations but Dark Mode still overrides the status bar color. – Andrew Kirna Sep 29 '19 at 23:43
  • @AndrewKirna Hard so say what the issue might be. Do you maybe want to post a question for that? – Frank Rupprecht Sep 30 '19 at 06:23
  • Ok, I’ll consider posting. It seems Apple wants me to use a dynamic color for my nav bars, however, I don’t want to do that because it uses my company’s branding. I’m guessing your solution has the same problem I’m experiencing (black status bar text in light mode; white status bar text in dark mode) despite overriding `preferredStatusBarStyle` in each view controller. Have you built it on Xcode 11 for iOS 13? – Andrew Kirna Sep 30 '19 at 14:09
  • Yes, works perfectly for me. Are you sure you override it in the main presented view controller? – Frank Rupprecht Sep 30 '19 at 14:23
  • The presented view controller is a subclass of a view controller that overrides the property. I use a base class. Do you think that’s an issue? – Andrew Kirna Sep 30 '19 at 14:45
  • I don't think so... Are you really sure you set the `UIViewControllerBasedStatusBarAppearance` in the `info.plist` to `YES`? – Frank Rupprecht Sep 30 '19 at 18:31
  • Yup. I also set “Status bar style” to “Light Content” in `info.plist`. Do you think that would affect it? – Andrew Kirna Sep 30 '19 at 19:01
  • It should not, but wouldn't hurt to test removing that key. Are you using a navigation controller? If so, this might help: https://stackoverflow.com/a/19365160/541016 – Frank Rupprecht Sep 30 '19 at 19:18
  • That answer is what I've been using for iOS < 13. It uses the old API of setting the `barStyle` property to control both the tint of bar buttons in the nav bar and the tint of the status bar (i.e. setting `barStyle = .black` accomplished my desired white text over a dark background). iOS 13 requires a new implementation... – Andrew Kirna Oct 22 '19 at 22:08
  • 1
    Frank, I followed a bunch of links related to your link above and eventually discovered what I needed! Thanks for the help. I posted my solution [below](https://stackoverflow.com/a/58514882/5739507)! – Andrew Kirna Oct 23 '19 at 03:07
  • I'm using Xcode 11.4 and right now, I have view controller presented on a custom UINavigationController and others that are presented on a native UINavigationController. childForStatusBarStyle is only called for the view controllers that are presented on the native UINavigationController. This is so annoying. Does anyone have any idea why ? – Mohamed Aziz Bessrour Apr 09 '20 at 21:59
6

You can write extension to UIStatusBarStyle:

extension UIStatusBarStyle {
    static var black: UIStatusBarStyle {
        if #available(iOS 13.0, *) {
            return .darkContent
        }
        return .default
    }
}

And then you can easily use in your ViewControllers:

override var preferredStatusBarStyle: UIStatusBarStyle {
    .black
}
buxik
  • 2,583
  • 24
  • 31
4

You can try to make your navigation bar always light

if #available(iOS 13.0, *) {
    navigationController?.navigationBar.overrideUserInterfaceStyle = .light
 }
shim
  • 9,289
  • 12
  • 69
  • 108
1

I have done something like this.

I have toggler function wich toggles Status bar style depending on View Controller displayed

func toggleLight() {
        self.navigationBar.barTintColor = AppColors.White

        isDarkStyle = false
        setNeedsStatusBarAppearanceUpdate()

    }

and here is most important part

override var preferredStatusBarStyle: UIStatusBarStyle {

        if #available(iOS 13.0, *) {
            return isDarkStyle ? .lightContent : .darkContent
        }
        return isDarkStyle ? .lightContent : .default
    }

Where isDarkStyle represents navigation bar background colour dark or light. If it is dark then text (content) should be light, if it is light than text (content) should be default or from iOS 13 dark.

To sum up: .lightContent, .darkContent displays independent of Dark Mode as it is supposed to do. While .default is susceptible to Dark Mode changes!

Michał Ziobro
  • 10,759
  • 11
  • 88
  • 143
0

If you're using a UINavigationController, override the preferredStatusBarStyle in an extension (or your own subclass) like this (just overriding preferredStatusBarStyle in your view controllers won't work):

extension UINavigationController {
  override open var preferredStatusBarStyle: UIStatusBarStyle {
    guard #available(iOS 13, *) else {
      return .default
    }
    return .darkContent
  }
}

And as Frank said, UIViewControllerBasedStatusBarAppearance must be set to YES in your info.plist

Ricky Padilla
  • 226
  • 5
  • 7
0

There are two ways to solve this problem. Define the childViewControllerForStatusBarStyle function in the UINavigationController descendant class:

@interface MyNavigationController : UINavigationController
...
@end

@implementation MyNavigationController
...
- (UIViewController *)childViewControllerForStatusBarStyle
{
    return self.topViewController;
}
...
@end

After that, you need to add the function preferredStatusBarStyle for each controller.

The second option is to define the preferredStatusBarStyle function for all controllers. But this function should not be located in the root controller, but in the descendant class of the UINavigationController.

@interface MyNavigationController : UINavigationController
...
@end

@implementation MyNavigationController
...
- (UIStatusBarStyle)preferredStatusBarStyle
{
    return UIStatusBarStyleLightContent;
}
...
@end

However, even in this case, it is necessary to define the function preferredStatusBarStyle for all controllers that hide the navigation bar (if any).

0

This extension helps you to change the status bar text color and support also iOS 13 https://stackoverflow.com/a/59767435/10512612

Mickael Belhassen
  • 2,970
  • 1
  • 25
  • 46
0

If anyone else finds that neither of the above answers work (as I did), there's a very specific edge-case if you have multiple windows within a UIWindowScene where it will use the topmost window rather than the key window to determine status bar appearance.

So for example, if you have your key window with a windowLevel set to 1 and a secondary window with a windowLevel set to 1.1, UIKit will (for whatever reason) ask the view controllers in your secondary window for their status bar appearance rather than the ones in the key window, even though its alpha is 0.

Our workaround was simply to only put the secondary window on a higher level when it was active, and lowering it below the key window when it was hidden.

Goos
  • 121
  • 6