How do I prevent iOS 13's Dark Mode from changing the text color in my app's status bar?

21,537

Solution 1

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 😎

Solution 2

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

Solution 3

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
}

Solution 4

You can try to make your navigation bar always light

if #available(iOS 13.0, *) {
    navigationController?.navigationBar.overrideUserInterfaceStyle = .light
 }

Solution 5

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!

Share:
21,537

Related videos on Youtube

EvGeniy Ilyin
Author by

EvGeniy Ilyin

Updated on August 05, 2021

Comments

  • EvGeniy Ilyin
    EvGeniy Ilyin almost 3 years

    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?

    • rmaddy
      rmaddy almost 5 years
      It sounds like you don't wish to support darkMode at all. You can opt out of supporting darkMode. That's been covered here.
    • EvGeniy Ilyin
      EvGeniy Ilyin almost 5 years
      yes You right, but > here < - where? I do not see link
    • rmaddy
      rmaddy almost 5 years
      I meant Stack Overflow. Search on opting out of dark mode.
    • DoesData
      DoesData almost 5 years
      > here < and > here <
    • shim
      shim over 4 years
      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.
  • Andrew Kirna
    Andrew Kirna over 4 years
    I am using both of those configurations but Dark Mode still overrides the status bar color.
  • Frank Rupprecht
    Frank Rupprecht over 4 years
    @AndrewKirna Hard so say what the issue might be. Do you maybe want to post a question for that?
  • Andrew Kirna
    Andrew Kirna over 4 years
    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?
  • Frank Rupprecht
    Frank Rupprecht over 4 years
    Yes, works perfectly for me. Are you sure you override it in the main presented view controller?
  • Andrew Kirna
    Andrew Kirna over 4 years
    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?
  • Frank Rupprecht
    Frank Rupprecht over 4 years
    I don't think so... Are you really sure you set the UIViewControllerBasedStatusBarAppearance in the info.plist to YES?
  • Andrew Kirna
    Andrew Kirna over 4 years
    Yup. I also set “Status bar style” to “Light Content” in info.plist. Do you think that would affect it?
  • Frank Rupprecht
    Frank Rupprecht over 4 years
    It should not, but wouldn't hurt to test removing that key. Are you using a navigation controller? If so, this might help: stackoverflow.com/a/19365160/541016
  • Andrew Kirna
    Andrew Kirna over 4 years
    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
    Andrew Kirna over 4 years
    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!
  • Marc Etcheverry
    Marc Etcheverry over 4 years
    Note that the extension method of doing override var does not work anymore in Xcode 11.4/iOS 13.4
  • alextudge
    alextudge about 4 years
    @MarcEtcheverry do you know what the alternative is for 13.4?
  • Marc Etcheverry
    Marc Etcheverry about 4 years
    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.
  • Mohamed Aziz Bessrour
    Mohamed Aziz Bessrour about 4 years
    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
    Mohamed Aziz Bessrour about 4 years
    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 ?
  • Andrew Kirna
    Andrew Kirna about 4 years
    @MarcEtcheverry, thanks for pointing that out. Do you have any documentation for this so I can update my answer?
  • Marc Etcheverry
    Marc Etcheverry about 4 years
    "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". developer.apple.com/library/archive/documentation/Cocoa/…
  • Marc Etcheverry
    Marc Etcheverry about 4 years
    You can follow all of links and answers here: 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.
  • Lance Samaria
    Lance Samaria about 4 years
    subclassing UINavigationController is the only thing that worked for me because my info.plist has View controller-based status bar appearance = YES
  • Ammar Mujeeb
    Ammar Mujeeb almost 4 years
    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..!