86

With the arrival of iOS 13 statusBar's view is no longer accessible trough:

value(forKey: "statusBar") as? UIView

Due to:

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'App called -statusBar or -statusBarWindow on UIApplication: this code must be changed as there's no longer a status bar or status bar window. Use the statusBarManager object on the window scene instead.'

But it's not clear how it should be used for changing colours as keyWindow?.windowScene?.statusBarManager does not appear to contain anything related to it.

I'm compiling my code with (iOS 10, *) compatibility, so I intend to continue using UIKit.

Any ideas regarding this subject?

Hugo Alonso
  • 6,684
  • 2
  • 34
  • 65
  • 3
    Why are you trying to manually change the status bar background color? By default it will match the color of your app. – rmaddy Jun 18 '19 at 15:13
  • It's a legacy app that includes a custom status bar colour making it look apart from the rest of the app – Hugo Alonso Jun 19 '19 at 08:21
  • 5
    There has never been a valid way to modify the status bar color. Such solutions always end up breaking eventually. Never dig into the private subview structure. – rmaddy Jun 19 '19 at 15:09
  • @HugoAlonso see my answer here: https://stackoverflow.com/questions/56556254/in-ios13-the-status-bar-background-colour-is-different-from-the-navigation-bar-i/57152709#57152709 – Mike Jul 22 '19 at 19:38
  • @rmaddy can you add an answer stating the fact that is not possible and what's the best approach so I can set it as the accepted one? – Hugo Alonso Oct 01 '19 at 13:42
  • https://www.youtube.com/watch?v=bZXk0Dwxopw.... refer this video – Akshay Jadhav Oct 14 '19 at 11:06
  • @hugoAlonso You can check this answer: https://stackoverflow.com/a/57848822/2677134 – Akshar Darji Oct 18 '19 at 05:36

16 Answers16

56

You can add some conditions or use first one. Just create some extension for UIApplication.

extension UIApplication {
var statusBarUIView: UIView? {
    if #available(iOS 13.0, *) {
        let tag = 38482
        let keyWindow = UIApplication.shared.windows.filter {$0.isKeyWindow}.first

        if let statusBar = keyWindow?.viewWithTag(tag) {
            return statusBar
        } else {
            guard let statusBarFrame = keyWindow?.windowScene?.statusBarManager?.statusBarFrame else { return nil }
            let statusBarView = UIView(frame: statusBarFrame)
            statusBarView.tag = tag
            keyWindow?.addSubview(statusBarView)
            return statusBarView
        }
    } else if responds(to: Selector(("statusBar"))) {
        return value(forKey: "statusBar") as? UIView
    } else {
        return nil
    }
  }
}

UPDATED: Sorry, I don't have enough time to test it in real projects, but it works in "Hello world" app. You can read more info about keyWindow and statusBarFrame in order to make it better.

extension UIApplication {
var statusBarUIView: UIView? {

    if #available(iOS 13.0, *) {
        let tag = 3848245

        let keyWindow = UIApplication.shared.connectedScenes
            .map({$0 as? UIWindowScene})
            .compactMap({$0})
            .first?.windows.first

        if let statusBar = keyWindow?.viewWithTag(tag) {
            return statusBar
        } else {
            let height = keyWindow?.windowScene?.statusBarManager?.statusBarFrame ?? .zero
            let statusBarView = UIView(frame: height)
            statusBarView.tag = tag
            statusBarView.layer.zPosition = 999999

            keyWindow?.addSubview(statusBarView)
            return statusBarView
        }

    } else {

        if responds(to: Selector(("statusBar"))) {
            return value(forKey: "statusBar") as? UIView
        }
    }
    return nil
  }
}
isHidden
  • 860
  • 5
  • 16
  • 4
    `keyWindow` is deprecated in iOS 13. – rmaddy Sep 25 '19 at 20:03
  • 2
    Why would you want to use something that's deprecated? – Hugo Alonso Oct 08 '19 at 12:57
  • @isHidden I was using the iOS version with np, updated to Xcode 11 and iOS 13 and got a crash, I checnged the extension code to your answer and no more crashes, thanks. But can you give an example on how to use your code information iOS 12 and iOS 13. Right now it just shows what to add in the extension – Lance Samaria Oct 11 '19 at 09:30
  • 1
    as pointed by @rmaddy, `keyWindow` as well as `statusBarFrame` are both deprecated – Preetam Jadakar Oct 18 '19 at 07:04
  • new to iOS dev. Can anyone tell me where to use this code snippet? in AppDelegate or viewController? – MMK Nov 07 '19 at 07:35
  • @HugoAlonso, There are no warnings in my projects, maybe the reason is their age. – isHidden Dec 11 '19 at 21:15
  • @MMK in my case i use it in viewController to have ability to change the statusBar colour for different VC. – isHidden Dec 11 '19 at 21:21
  • @LanceSamaria to change statusBar colour write this in some place of your code. (For example in viewWillAppear or in AppDelegate): UIApplication.shared.statusBarUIView?.backgroundColor = .yellow – isHidden Dec 11 '19 at 21:26
  • @isHidden thanks for getting back to me. I figured out how to use it later that day after I asked you. I forgot what I did exactly but maybe it was what you just wrote? Any much appreciated. Happy Coding! – Lance Samaria Dec 11 '19 at 21:59
  • Where is stated that the view is the one with tag 3848245? – Hugo Alonso Dec 12 '19 at 11:53
  • @HugoAlonso, it works for me. The tag for each subview is 0 by default. But maybe in some case the window can already has a subview with same tag. If you want to be sure that your tag is unique you can do the next: 1) create function that get UIView description and convert it to Int hash value, than use it like a tag to identify your view. 2) create function that generate random integer value and check if there are the same in window, if yes - generate new random value, no - use it like a tag. 3) try to find some docs where is stated that your tag is unique. – isHidden Dec 18 '19 at 15:59
  • @isHidden, how can I apply it to swiftUI –  Apr 09 '21 at 12:54
  • @Skysoft13 it's completely different story for SwiftUI. Here's a solution I use https://stackoverflow.com/questions/62207272/updating-the-uinavigationbar-color-only-applies-a-tint-not-sure-why – Eduard Streltsov Oct 10 '21 at 15:56
  • 'windows' was deprecated in iOS 15.0: Use UIWindowScene.windows on a relevant window scene instead – Sentry.co Dec 11 '21 at 14:09
53

Unfortunately Apple deprecated some of the mentioned methods of accessing the status bar and editing its attributes. You will have to use the StatusBarManager object of the WindowScene. The following method works for iOS 13 and above:

extension UINavigationController {

    func setStatusBar(backgroundColor: UIColor) {
        let statusBarFrame: CGRect
        if #available(iOS 13.0, *) {
            statusBarFrame = view.window?.windowScene?.statusBarManager?.statusBarFrame ?? CGRect.zero
        } else {
            statusBarFrame = UIApplication.shared.statusBarFrame
        }
        let statusBarView = UIView(frame: statusBarFrame)
        statusBarView.backgroundColor = backgroundColor
        view.addSubview(statusBarView)
    }

}
Alaa Othman
  • 1,109
  • 12
  • 16
  • 3
    Best answer of all – Patrick Feb 04 '20 at 11:03
  • 1
    Perfect Solution! – Vinayak Bhor May 26 '20 at 08:10
  • How about the check if the statusBar view was added already? This is to make sure we do not add same subview again and again. – D. B. Oct 14 '20 at 15:11
  • the problem with this solution is that statusBarFrame is nil in case you call it in viewDidLoad or viewWillAppear. Works only in viewDidAppear what does not sound as a best place to set up UI – Artiom Nov 16 '20 at 16:29
  • 2
    If you are unsure how to call this like I was, in viewDidAppear (thanks @Artiom) call self.navigationController?.setStatusBar(backgroundColor: yourColor) AND self.navigationController?.navigationBar.setNeedsLayout() – Ben Nov 29 '20 at 16:22
  • Alternatively, you can call self.navigationController?.setStatusBar(backgroundColor: yourColor) in viewWillAppear – Ben Nov 29 '20 at 16:24
8

I have encountered this issue before. My application got crash while I run this code using XCode 11 and Swift 5.0.

Previous Code:-

UIApplication.shared.statusBarView?.backgroundColor = UIColor.init(red: 243/250, green: 243/250, blue: 243/250, alpha: 1)

Just Changed to:-

if #available(iOS 13.0, *) {
    let statusBar = UIView(frame: UIApplication.shared.keyWindow?.windowScene?.statusBarManager?.statusBarFrame ?? CGRect.zero)
     statusBar.backgroundColor = UIColor.init(red: 243/250, green: 243/250, blue: 243/250, alpha: 1)
     UIApplication.shared.keyWindow?.addSubview(statusBar)
} else {
     UIApplication.shared.statusBarView?.backgroundColor = UIColor.init(red: 243/250, green: 243/250, blue: 243/250, alpha: 1)
}

Now my problem solved. Happy coding.

Hugo Alonso
  • 6,684
  • 2
  • 34
  • 65
Ajeet
  • 313
  • 4
  • 14
  • 6
    `keyWindow` is deprecated in iOS 13. – rmaddy Oct 18 '19 at 15:00
  • let keyWindow = UIApplication.shared.connectedScenes .filter({$0.activationState == .foregroundActive}) .map({$0 as? UIWindowScene}) .compactMap({$0}) .first?.windows .filter({$0.isKeyWindow}).first You can call directly as:- keyWindow?.addSubview(statusBar) – Ajeet Nov 14 '19 at 07:22
  • Don't put code in a comment, update your answer as needed. Keep in mind that there may be two active scenes so simply grabbing the first could be wrong. – rmaddy Nov 14 '19 at 07:55
  • For keyWindows: UIApplication.shared.windows.filter {$0.isKeyWindow}.first – Roman Nov 22 '19 at 08:04
7

for swift 5.0 I've done this to change background color,

    if #available(iOS 13.0, *) {
           let window = UIApplication.shared.windows.filter {$0.isKeyWindow}.first 
           // Reference - https://stackoverflow.com/a/57899013/7316675
           let statusBar = UIView(frame: window?.windowScene?.statusBarManager?.statusBarFrame ?? CGRect.zero)
           statusBar.backgroundColor = .white
           window?.addSubview(statusBar)
    } else {
           UIApplication.shared.statusBarView?.backgroundColor = .white
           UIApplication.shared.statusBarStyle = .lightContent
    }

https://medium.com/@trivediniki94/surprises-after-upgrading-to-xcode-11-ios-13-b52b36e05fa8

Niki
  • 1,566
  • 1
  • 19
  • 36
  • That's better, but it still isn't ideal. What happens when the app is showing two different scenes on an iPad? This code only shows the extra view above one of the scenes and not both. – rmaddy Oct 22 '19 at 15:24
  • @rmaddy So should i put a for loop, and which would execute the same code for all windows ? – Niki Oct 23 '19 at 10:47
  • 1
    Any chance of someone providing an Objective C version of this? – Steve Brooker Oct 25 '19 at 21:00
  • Value of type '[UIWindow]' has no member 'addSubview' – Marlhex Oct 30 '19 at 17:23
  • @Daniella I've updated my code, would you please check your code now! – Niki Oct 31 '19 at 09:21
  • @jayantrawat yes, In didFinishLaunchingWithOptions application method within APPDELEGATE. – Niki Feb 13 '20 at 08:54
  • @Niki its not working for me with Xcode 11/iOS13 & UIApplication.shared.statusBarView is also not available in Xcode11... – jayant rawat Feb 13 '20 at 09:18
  • This works for me but... (I am making black status bar with white text, working without a NavController). I found that in my first viewController I had to put the code from this answer in viewDidAppear for it to work. In subsequent viewControllers in the flow I put it in viewWillAppear. I am also using this solution - which works for a non-NavController scenario: https://stackoverflow.com/a/17768797/826946. That makes the statusbar text white. Also not using the pre-OS13 part – Andy Weinstein Aug 18 '21 at 08:35
  • "Value of type 'UIApplication' has no member 'statusBarView'" error occured for Swift 5. – Nilay Dagdemir Sep 27 '21 at 09:45
6

This worked for me in Swift 5

   override func viewDidLoad() {
      super.viewDidLoad()

      if #available(iOS 13, *)
      {
          let statusBar = UIView(frame: (UIApplication.shared.keyWindow?.windowScene?.statusBarManager?.statusBarFrame)!)
          statusBar.backgroundColor = #colorLiteral(red: 0.2346, green: 0.3456, blue: 0.5677, alpha: 1)
          UIApplication.shared.keyWindow?.addSubview(statusBar)
      } else {
         // ADD THE STATUS BAR AND SET A CUSTOM COLOR
         let statusBar: UIView = UIApplication.shared.value(forKey: "statusBar") as! UIView
         if statusBar.responds(to:#selector(setter: UIView.backgroundColor)) {
            statusBar.backgroundColor = #colorLiteral(red: 0.2346, green: 0.3456, blue: 0.5677, alpha: 1)
         }
         UIApplication.shared.statusBarStyle = .lightContent
      }
   }
C13
  • 141
  • 2
  • 4
  • I'm getting a warning of : 'keyWindow' was deprecated in iOS 13.0: Should not be used for applications that support multiple scenes as it returns a key window across all connected scenes – justdan0227 Sep 06 '21 at 15:58
4

Use the following code:

if (@available(iOS 13, *)) {
    let statusBar1 =  UIView()
    statusBar1.frame = UIApplication.shared.statusBarFrame
    statusBar1.backgroundColor = UIColor.red
    UIApplication.shared.keyWindow?.addSubview(statusBar1)
}

to achieve this result:

enter image description here

App Dev Guy
  • 5,396
  • 4
  • 31
  • 54
Tajinder singh
  • 738
  • 1
  • 9
  • 18
  • 2
    'statusBarFrame' was deprecated in iOS 13.0: Use the statusBarManager property of the window scene instead. 'keyWindow' was deprecated in iOS 13.0: Should not be used for applications that support multiple scenes as it returns a key window across all connected scenes... – Giuseppe Garassino Sep 11 '20 at 08:52
4

Tested and 100% worked for me

func statusBarColorChange(){

    if #available(iOS 13.0, *) {

            let statusBar = UIView(frame: UIApplication.shared.keyWindow?.windowScene?.statusBarManager?.statusBarFrame ?? CGRect.zero)
            statusBar.backgroundColor = AppThemeColor
            statusBar.tag = 100
            UIApplication.shared.keyWindow?.addSubview(statusBar)

    } else {

            let statusBar = UIApplication.shared.value(forKeyPath: "statusBarWindow.statusBar") as? UIView
            statusBar?.backgroundColor = AppThemeColor

        }
    }
}

remove status bar from the key window

func removeStatusBar(){

    if #available(iOS 13.0, *) {

        UIApplication.shared.keyWindow?.viewWithTag(100)?.removeFromSuperview()

    }
}

in viewDidLoad and viewWillAppear call above function

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    removeStatusBar()
    statusBarColorChange()
}

override func viewDidLoad() {
    super.viewDidLoad()

    removeStatusBar()
    statusBarColorChange()

}
4

KeyWindow is deprecated in iOS13. You can use this extension for swift 5 and iOS 13 to the top

extension UIApplication {

    var statusBarUIView: UIView? {

        if #available(iOS 13.0, *) {
            let tag = 3848245

            let keyWindow: UIWindow? = UIApplication.shared.windows.filter {$0.isKeyWindow}.first

            if let statusBar = keyWindow?.viewWithTag(tag) {
                return statusBar
            } else {
                let height = keyWindow?.windowScene?.statusBarManager?.statusBarFrame ?? .zero
                let statusBarView = UIView(frame: height)
                statusBarView.tag = tag
                statusBarView.layer.zPosition = 999999

                keyWindow?.addSubview(statusBarView)
                return statusBarView
            }

        } else {

            if responds(to: Selector(("statusBar"))) {
                return value(forKey: "statusBar") as? UIView
            }
        }
        return nil
      }
}

And use it in your didFinishLaunchingWithOptions in AppDelegate class like :

UIApplication.shared.statusBarUIView?.backgroundColor = .red(any color)
iSibDev
  • 340
  • 5
  • 18
3
if (@available(iOS 13, *))
{    
    UIView *statusBar = [[UIView alloc]initWithFrame:[UIApplication sharedApplication].keyWindow.windowScene.statusBarManager.statusBarFrame] ;
    statusBar.backgroundColor = [UIColor redColor];
    [[UIApplication sharedApplication].keyWindow addSubview:statusBar];

 }
Nick
  • 875
  • 6
  • 20
BASIL BABY
  • 73
  • 2
2

you can try this

if (@available(iOS 13, *))
{
    UIView *_localStatusBar = [[UIApplication sharedApplication].keyWindow.windowScene.statusBarManager performSelector:@selector(createLocalStatusBar)];
    statusBar = [_localStatusBar performSelector:@selector(statusBar)];
}
else
{
    statusBar = [[UIApplication sharedApplication] valueForKey:@"statusBar"];
}
Ewane Shen
  • 63
  • 2
0
Use this Extension:

extension UINavigationController {

func setStatusBar(backgroundColor: UIColor) {
    let statusBarFrame: CGRect
    if #available(iOS 13.0, *) {
        statusBarFrame = view.window?.windowScene?.statusBarManager?.statusBarFrame ?? CGRect.zero
    } else {
        statusBarFrame = UIApplication.shared.statusBarFrame
    }
    let statusBarView = UIView(frame: statusBarFrame)
    statusBarView.backgroundColor = backgroundColor
    view.addSubview(statusBarView)
}}
Avneesh Agrawal
  • 916
  • 6
  • 11
0

If you want to make it for all your app, you can add to you Info.plist these keys and values ​​for them

View controller-based status bar appearance - NO
Status bar style - Light Content

enter image description here

Gleb
  • 146
  • 7
  • This messes up SwiftUI a little. `.statusBar(hidden: true)` no longer works. If `UIViewControllerBasedStatusBarAppearance` is `YES` then SwiftUI can hide the status bar but can't have a light/white appearance. – John Endres Apr 12 '23 at 17:16
-1

this is an ObjC version of most voted answer for those like me who are still using it:

create a category of UIApplication and add it to your project:

@implementation UIApplication (iOS13PorcoDiDio)

- (UIView*) statusBar
{

    if([UIDevice getOSVersion] >= 13)
    {

        const NSUInteger k_TAG_STATUSBAR = 38482458385;

        UIView * vStatusBar = [[UIApplication sharedApplication].keyWindow viewWithTag:k_TAG_STATUSBAR];
        if(vStatusBar != nil)

            return vStatusBar;

        else {

            UIView *vStatusBar = [[UIView alloc] initWithFrame:[UIApplication sharedApplication].statusBarFrame];
            [vStatusBar setTag:k_TAG_STATUSBAR];
            [[UIApplication sharedApplication].keyWindow addSubview:vStatusBar];

            return vStatusBar;

        }

    } else if([UIApplication respondsToSelector:@selector(statusBar)])

        return (UIView*)[UIApplication sharedApplication].statusBar;

    else

        return nil;


}

@end
Fabio Napodano
  • 1,210
  • 1
  • 16
  • 17
-1

This is the best answer I have ever seen.. Cheers

    if #available(iOS 13.0, *) {
    let app = UIApplication.shared
    let statusBarHeight: CGFloat = app.statusBarFrame.size.height

    let statusbarView = UIView()
        statusbarView.backgroundColor = ColorPalette.grayChateau  
    view.addSubview(statusbarView)

    statusbarView.translatesAutoresizingMaskIntoConstraints = false
    statusbarView.heightAnchor
        .constraint(equalToConstant: statusBarHeight).isActive = true
    statusbarView.widthAnchor
        .constraint(equalTo: view.widthAnchor, multiplier: 1.0).isActive = true
    statusbarView.topAnchor
        .constraint(equalTo: view.topAnchor).isActive = true
    statusbarView.centerXAnchor
        .constraint(equalTo: view.centerXAnchor).isActive = true

} else {
    let statusBar = UIApplication.shared.value(forKeyPath: "statusBarWindow.statusBar") as? UIView
    statusBar?.backgroundColor = UIColor.red
 }
Archu Mohan
  • 199
  • 1
  • 5
  • 14
  • 'statusBarFrame' was deprecated in iOS 13.0: Use the statusBarManager property of the window scene instead. – Marlhex Oct 30 '19 at 17:21
-1

I think the simplest way is to use NavigationController instead of ViewController. Changing of Navigation Bar background using storyboard will also reflect on status bar

NavigationController

shalonteoh
  • 1,994
  • 2
  • 15
  • 17
  • 1
    It would be great to see which settings in the attribute inspector were changed, cause when I changed the title background in NavigationController it didn't affect the status bar background – Eduard Streltsov Oct 10 '21 at 18:34
-2

I do this, can get statusBar,but set statusBar backgroundColor do not work

UIView *statusBar;
if (@available(iOS 13, *)) {
    UIView *_localStatusBar = [[UIApplication sharedApplication].keyWindow.windowScene.statusBarManager performSelector:@selector(createLocalStatusBar)];
    statusBar = [_localStatusBar performSelector:@selector(statusBar)];
}
else {
    statusBar = [[UIApplication sharedApplication] valueForKey:@"statusBar"];
}
if ([statusBar respondsToSelector:@selector(setBackgroundColor:)]) {
    statusBar.backgroundColor = [UIColor redColor];
}
Kampai
  • 22,848
  • 21
  • 95
  • 95
sandy
  • 47
  • 3