Reputation: 3121
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.)
Upvotes: 282
Views: 148325
Reputation: 971
I was having an issue where my modally presented view controller was never getting preferredStatusBarStyle
called on it; I realized this was because I had embedded my modally presented view controller within a UINavigationController
in the storyboard. I had to create a subclass for the UINavigationController
and assign it to my storyboard embed, with the following code for my UINavigationController
subclass instance:
class MyCustomNavigationController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
self.modalPresentationCapturesStatusBarAppearance = true
}
override var preferredStatusBarStyle: UIStatusBarStyle {
if #available(iOS 13.0, *) {
return .darkContent
} else {
return .default
}
}
}
doing these two things allowed the status bar to correctly remain black on iOS 14 when presenting the modal.
Upvotes: -1
Reputation: 38667
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 childForStatusBarStyle
(Swift) / childViewControllerForStatusBarStyle
(ObjC).
Override your UINavigationController in your class:
class MyRootNavigationController: UINavigationController {
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
override var childForStatusBarStyle: UIViewController? {
return nil
}
}
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 childForStatusBarStyle: UIViewController? {
return nil
}
}
Upvotes: 18
Reputation: 1794
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.
Upvotes: 0
Reputation: 576
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];
Upvotes: 2
Reputation: 1396
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:
info.plist
, add the following property:
UIUserInterfaceStyle
(aka. "User Interface Style")Override preferredStatusBarStyle
within UINavigationController
preferredStatusBarStyle
(doc) - The preferred status bar style for the view controllerSubclass or extend UINavigationController
class MyNavigationController: UINavigationController {
override var preferredStatusBarStyle: UIStatusBarStyle {
.lightContent
}
}
OR
extension UINavigationController {
open override var preferredStatusBarStyle: UIStatusBarStyle {
.lightContent
}
}
Override childForStatusBarStyle
within UINavigationController
childForStatusBarStyle
(doc) - Called when the system needs the view controller to use for determining status bar style"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."
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 stackvisibleViewController
(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 😎
Upvotes: 14
Reputation: 211
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.
Upvotes: 4
Reputation: 1613
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
}
}
Upvotes: 88
Reputation: 668
As mentioned in selected answer, root cause is to check your window root view controller object.
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
}
}
UIViewControllerBasedStatusBarAppearance
key in info.plist
as it true by defaultIn 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.
Upvotes: 13
Reputation: 1009
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
}
}
Upvotes: 23
Reputation: 5945
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
}
Upvotes: 1
Reputation: 4536
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.
Upvotes: 17
Reputation: 14734
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;
Upvotes: 1064
Reputation: 3789
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
}
}
Upvotes: 3
Reputation: 2189
If your viewController is under UINavigationController.
Subclass UINavigationController and add
override var preferredStatusBarStyle: UIStatusBarStyle {
return topViewController?.preferredStatusBarStyle ?? .default
}
ViewController's preferredStatusBarStyle
will be called.
Upvotes: 6
Reputation: 383
@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
}
Upvotes: 7
Reputation: 455
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.
Upvotes: 0
Reputation: 6730
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;
}
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.
Upvotes: 122
Reputation: 342
Swift 3 iOS 10 Solution:
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
Upvotes: 1
Reputation: 1859
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).
Upvotes: 1
Reputation: 1828
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?)
Upvotes: 103
Reputation: 529
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.
Upvotes: 4
Reputation: 9266
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
}
Upvotes: 0
Reputation: 30488
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.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// status bar appearance code
[[UINavigationBar appearance] setBarStyle:UIBarStyleBlack];
return YES;
}
Upvotes: 15
Reputation: 9441
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.
Upvotes: 0
Reputation: 4751
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.
Upvotes: 9