preferredStatusBarStyle isn't called

2019-01-01 16:59发布

问题:

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

回答1:

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.



回答2:

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;


回答3:

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



回答4:

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

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


回答5:

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;
}


回答6:

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.



回答7:

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
    }
}


回答8:

@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
}


回答9:

If your viewController is under UINavigationController.

Subclass UINavigationController and add

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

ViewController\'s preferredStatusBarStyle will be called.



回答10:

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.



回答11:

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
    }

}


回答12:

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.

To do it for one UINavigationController (my recommendation):

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

To do it for all UINavigationController (warning: it affects UIDocumentPickerViewController, UIImagePickerController, etc.):

extension UINavigationController {
    open override var preferredStatusBarStyle: UIStatusBarStyle {
        return .lightContent
    }
    open override var childViewControllerForStatusBarStyle: UIViewController? {
        return nil
    }
}


回答13:

Swift 3 iOS 10 Solution:

override var preferredStatusBarStyle: UIStatusBarStyle {
    return .lightContent
 }


回答14:

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.



回答15:

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
}


回答16:

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



回答17:

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.



回答18:

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.



回答19:

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
}


回答20:

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