preferredStatusBarStyle没有被调用

我使用这个线程来覆盖-preferredStatusBarStyle,但它没有被调用。 有什么选项我可以改变来启用它吗?(我在我的项目中使用xib)

142236 次浏览

可能的根本原因

我也遇到了同样的问题,我发现这是因为我没有在我的应用程序窗口中设置根视图控制器。

我在其中实现了preferredStatusBarStyleUIViewController被用于UITabBarController,它控制了屏幕上视图的外观。

当我设置根视图控制器指向这个UITabBarController时,状态栏的变化开始正确工作,正如预期的那样(并且preferredStatusBarStyle方法被调用)。

(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
... // other view controller loading/setup code


self.window.rootViewController = rootTabBarController;
[self.window makeKeyAndVisible];
return YES;
}

替代方法(在iOS 9中已弃用)

或者,你可以在适当的情况下,在每个视图控制器中调用以下方法之一,这取决于它的背景颜色,而不是必须使用setNeedsStatusBarAppearanceUpdate:

[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];

[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleDefault];

注意,如果你使用这个方法,你还需要在plist文件中将UIViewControllerBasedStatusBarAppearance设置为NO

Hippo的回答:如果你正在使用UINavigationController,那么最好添加一个类别:

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

这种解决方案可能比切换到即将被弃用的行为要好。

对于任何使用UINavigationController的人:

UINavigationController不会将preferredStatusBarStyle调用转发给它的子视图控制器。相反,它管理自己的状态——就像它应该做的那样,它在屏幕顶部绘制状态栏,因此应该对状态栏负责。因此,在导航控制器中实现vc中的preferredStatusBarStyle将什么都不做——它们永远不会被调用。

诀窍在于UINavigationController使用什么来决定为UIStatusBarStyleDefaultUIStatusBarStyleLightContent返回什么。它基于UINavigationBar.barStyle。默认值(UIBarStyleDefault)导致前景UIStatusBarStyleDefault状态栏为黑色。而UIBarStyleBlack将给出一个UIStatusBarStyleLightContent状态栏。

TL;博士:

如果你想在UINavigationController上使用UIStatusBarStyleLightContent:

self.navigationController.navigationBar.barStyle = UIBarStyleBlack;

所以我实际上添加了一个category到UINavigationController,但是使用了这些方法:

-(UIViewController *)childViewControllerForStatusBarStyle;
-(UIViewController *)childViewControllerForStatusBarHidden;

让它们返回当前可见的UIViewController。这让当前可见视图控制器设置它自己的首选样式/可见性。

下面是它的完整代码片段:

迅速:

extension UINavigationController {


public override func childViewControllerForStatusBarHidden() -> UIViewController? {
return self.topViewController
}


public override func childViewControllerForStatusBarStyle() -> UIViewController? {
return self.topViewController
}
}

在objective - c中:

@interface UINavigationController (StatusBarStyle)


@end


@implementation UINavigationController (StatusBarStyle)


-(UIViewController *)childViewControllerForStatusBarStyle {
return self.topViewController;
}


-(UIViewController *)childViewControllerForStatusBarHidden {
return self.topViewController;
}


@end

为了更好地衡量,这是它如何在UIViewController中实现的:

在斯威夫特

override public func preferredStatusBarStyle() -> UIStatusBarStyle {
return .LightContent
}


override func prefersStatusBarHidden() -> Bool {
return false
}

在objective - c中

-(UIStatusBarStyle)preferredStatusBarStyle {
return UIStatusBarStyleLightContent; // your own style
}


- (BOOL)prefersStatusBarHidden {
return NO; // your own visibility code
}

最后,确保你的app plist 将“基于视图控制器的状态栏外观”设置为NO。要么删除这一行,要么将其设置为YES(我相信这是iOS 7的默认设置?)

这是我解决这个问题的方法。

定义一个名为AGViewControllerAppearance的协议。

AGViewControllerAppearance.h

#import <Foundation/Foundation.h>


@protocol AGViewControllerAppearance <NSObject>


@optional


- (BOOL)showsStatusBar;
- (BOOL)animatesStatusBarVisibility;
- (UIStatusBarStyle)preferredStatusBarStyle;
- (UIStatusBarAnimation)prefferedStatusBarAnimation;


@end

ui上定义一个名为升级的类别。

ui + Upgrade.h

#import <UIKit/UIKit.h>


@interface UIViewController (Upgrade)


//
//  Replacements
//


- (void)upgradedViewWillAppear:(BOOL)animated;


@end

ui + 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

现在,是时候说你的视图控制器正在实现AGViewControllerAppearance协议。

例子:

@interface XYSampleViewController () <AGViewControllerAppearance>


... the rest of the interface


@end
当然,你可以从协议中实现其余的方法(showsStatusBaranimatesStatusBarVisibilityprefferedStatusBarAnimation),并且ui +升级将完成适当的工作

.根据它们提供的值进行自定义

UINavigationController中将状态栏颜色更改为白色是正确的。

如果有人想通过在AppDelegate中编写代码来实现相同的结果,那么使用下面的代码并在AppDelegate's didFinishLaunchingWithOptions方法中编写它。

不要忘记在.plist文件中将UIViewControllerBasedStatusBarAppearance设置为YES,否则更改将不会反映。

代码

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// status bar appearance code
[[UINavigationBar appearance] setBarStyle:UIBarStyleBlack];


return YES;
}
如果有人在使用UISearchController时遇到这个问题。 只需创建UISearchController的一个新子类,然后将下面的代码添加到该类:

override func preferredStatusBarStyle() -> UIStatusBarStyle {
return .LightContent
}

iOS 7中的UIStatusBarStyle

iOS 7的状态栏是透明的,它后面的视图是透视的。

状态栏的样式是指其内容的外观。在ios7中,状态栏内容要么是暗的(UIStatusBarStyleDefault),要么是亮的(UIStatusBarStyleLightContent)。UIStatusBarStyleBlackTranslucentUIStatusBarStyleBlackOpaque在iOS 7.0中已弃用。请改用UIStatusBarStyleLightContent

如何改变UIStatusBarStyle

如果状态栏下面是一个导航栏,状态栏的样式将被调整以匹配导航栏的样式(UINavigationBar.barStyle):

具体来说,如果导航栏的样式是UIBarStyleDefault,状态栏的样式将是UIStatusBarStyleDefault;如果导航栏样式是UIBarStyleBlack,状态栏样式将是UIStatusBarStyleLightContent

如果状态栏下面没有导航栏,状态栏样式可以在应用程序运行时由单个视图控制器控制和更改。

-[UIViewController preferredStatusBarStyle]是iOS 7中添加的新方法。它可以被重写以返回首选的状态栏样式:

- (UIStatusBarStyle)preferredStatusBarStyle
{
return UIStatusBarStyleLightContent;
}

如果状态栏样式应该由子视图控制器而不是self控制,则重写-[UIViewController childViewControllerForStatusBarStyle]以返回该子视图控制器。

如果你选择不使用这种行为,并通过使用-[UIApplication statusBarStyle]方法设置状态栏样式,将UIViewControllerBasedStatusBarAppearance键添加到应用程序的Info.plist文件中,并赋予其值NO。

在Swift中,对于任何类型的UIViewController:

在你的AppDelegate集中:

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
window!.rootViewController = myRootController
return true
}

myRootController可以是任何类型的UIViewController,例如UITabBarControllerUINavigationController

然后,像这样重写这个根控制器:

class RootController: UIViewController {
override func preferredStatusBarStyle() -> UIStatusBarStyle {
return .LightContent
}
}

这将改变状态栏在整个应用程序中的外观,因为根控制器只负责状态栏的外观。

记住在你的Info.plist中将属性View controller-based status bar appearance设置为YES来让这个工作(这是默认的)。

NavigationController或TabBarController是需要提供样式的。这是我如何解决:https://stackoverflow.com/a/39072526/242769

在UINavigationController上,preferredStatusBarStyle不会被调用,因为它的topViewController优先于self。所以,要在UINavigationController上调用preferredStatusBarStyle,你需要改变它的childForStatusBarStyle (Swift) / childViewControllerForStatusBarStyle (ObjC)。

建议

重写你类中的UINavigationController:

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

非推荐替代方案

要为所有UINavigationController做这件事,你可以在一个扩展中重写(警告:它会影响UIDocumentPickerViewController, UIImagePickerController等),但是你可能不应该根据Swift文档来做:

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

Swift 3 iOS 10解决方案:

override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}

注意在使用self.navigationController.navigationBar.barStyle = UIBarStyleBlack;解决方案时

请确保进入你的plist并设置“基于视图控制器的状态栏外观”为YES。如果不是,它将不起作用。

上面的@serenn的回答对于UINavigationControllers来说仍然是一个很好的例子。然而,对于swift 3, childViewController函数已被更改为vars。所以UINavigationController扩展码应该是:

override open var childViewControllerForStatusBarStyle: UIViewController? {
return topViewController
}


override open var childViewControllerForStatusBarHidden: UIViewController? {
return topViewController
}

然后在视图控制器中,它应该指示状态栏的样式:

override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}

如果你的viewController在UINavigationController下面。

子类UINavigationController并添加

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

ViewController的preferredStatusBarStyle将被调用。

如果有人正在使用一个导航控制器,并希望他们所有的导航控制器都有黑色风格,你可以在Swift 3中写一个扩展到UINavigationController,它将应用于所有的导航控制器(而不是一次分配给一个控制器)。

extension UINavigationController {


override open func viewDidLoad() {
super.viewDidLoad()


self.navigationBar.barStyle = UIBarStyle.black
}


}

对于任何仍然与此斗争的人来说,这个简单的扩展在swift中应该为您解决这个问题。

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

我的应用程序使用了所有三个:UINavigationControllerUISplitViewControllerUITabBarController,因此它们似乎都控制了状态栏,并将导致preferedStatusBarStyle不被调用。要覆盖此行为,您可以创建一个扩展,就像前面提到的其他答案一样。这是一个扩展,这三个,在Swift 4。希望苹果对这类事情能更清楚些。

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

编辑:针对Swift 4.2 API更改的更新

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

除了serenn的回答,如果你用modalPresentationStyle(例如.overCurrentContext)来呈现一个视图控制器,你也应该在新呈现的视图控制器上调用这个:

presentedViewController.modalPresentationCapturesStatusBarAppearance = true

不要忘记在呈现的视图控制器中重写preferredStatusBarStyle

大多数答案不包括UINavigationControllerchildViewControllerForStatusBarStyle方法的良好实现。根据我的经验,你应该处理这样的情况,当透明的视图控制器在导航控制器。在这些情况下,你应该将控制权传递给你的模态控制器(visibleViewController),但当它消失时不要这样做。

override var childViewControllerForStatusBarStyle: UIViewController? {
var childViewController = visibleViewController
if let controller = childViewController, controller.isBeingDismissed {
childViewController = topViewController
}
return childViewController?.childViewControllerForStatusBarStyle ?? childViewController
}

Swift 4.2及以上版本

正如选择答案中提到的,根本原因是检查你的窗口根视图控制器对象。

流结构的可能情况

    <李> 自定义UIViewController对象是窗口根视图控制器 < br > < br > 你的窗口根视图控制器是一个UIViewController对象,它会根据你的应用程序流进一步添加或删除导航控制器或选项卡控制器。< br > < br > 这种流通常用于如果你的应用程序在导航堆栈上有前登录流而没有选项卡和后登录流有选项卡,可能每个选项卡都有导航控制器。
    < br > <李> TabBarController对象是窗口根视图控制器 < br > < br > 这是窗口根视图控制器是tabBarController的流程可能每个选项卡都有导航控制器。
    < br > <李> NavigationController对象是窗口根视图控制器 < br > < br > 这是窗口根视图控制器为navigationController的流。
    我不确定是否有可能在现有的导航控制器中添加选项卡栏控制器或新的导航控制器。但如果有这种情况,我们需要将状态栏样式控件传递给下一个容器。因此,我在UINavigationController扩展中添加了相同的检查,以查找childForStatusBarStyle

使用以下扩展,它处理以上所有场景 -

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
}
}
  • 你不需要在info.plist中使用UIViewControllerBasedStatusBarAppearance键,因为它默认为真

对于更复杂的流程,需要考虑的要点

  • 如果您以模式方式呈现新流,它将脱离现有的状态栏样式流。因此,假设你正在呈现一个NewFlowUIViewController,然后向NewFlowUIViewController添加新的导航或选项卡控制器,然后添加扩展NewFlowUIViewController来进一步管理视图控制器的状态栏样式。

  • 如果你在以模式方式呈现时设置了modalPresentationStyle而不是fullScreen,你必须将modalPresentationCapturesStatusBarAppearance设置为true,以便呈现的视图控制器必须接收状态栏外观控制。

在我的例子中,我意外地将视图/导航控制器表示为UIModalPresentationStyle.overFullScreen,这导致preferredStatusBarStyle未被调用。切换回UIModalPresentationStyle.fullScreen后,一切正常。

iOS 13解决方案

UINavigationControllerUIViewController的子类(谁知道🙃)!

因此,当呈现嵌入在导航控制器中的视图控制器时,你并没有真正呈现嵌入的视图控制器;你正在呈现导航控制器!UINavigationController,作为UIViewController的子类,继承了preferredStatusBarStylechildForStatusBarStyle,你可以根据需要进行设置。

以下任何一种方法都有效:

  1. 完全退出黑暗模式
      info.plist中,添加以下属性:
      • Key - UIUserInterfaceStyle (aka。“用户界面风格”)
      • 价值-光
      • 李< / ul > < / > 李< / ul > < / >
      • < p > 在__ABC1中覆盖preferredStatusBarStyle

        • preferredStatusBarStyle (医生) -视图控制器的首选状态栏样式
        • 继承或扩展UINavigationController

          class MyNavigationController: UINavigationController {
          override var preferredStatusBarStyle: UIStatusBarStyle {
          .lightContent
          }
          }
          

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

          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.

P.S. My code uses Swift 5.1 syntax 😎

对于iOS 13.4, UINavigationController类别中的preferredStatusBarStyle方法将不会被调用,swizzling似乎是不需要使用子类的唯一选项。

例子:

分类标题:

@interface UINavigationController (StatusBarStyle)
+ (void)setUseLightStatusBarStyle;
@end

实现:

#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

在AppDelegate.h中的用法:

#import "UINavigationController+StatusBarStyle.h"


[UINavigationController setUseLightStatusBarStyle];

从Xcode 11.4开始,覆盖UINavigationController扩展中的preferredStatusBarStyle属性不再有效,因为它将不会被调用。

navigationBarbarStyle设置为.black确实有效,但如果你向导航栏添加子视图,这些子视图可能在明暗模式下具有不同的外观,则会增加不必要的副作用。因为通过将barStyle设置为黑色,嵌入在导航栏中的视图的userInterfaceStyle将始终具有userInterfaceStyle.dark,而不管应用程序的userInterfaceStyle如何。

我提出的适当解决方案是通过添加UINavigationController的子类并在那里覆盖preferredStatusBarStyle。如果你使用这个自定义的UINavigationController为你所有的视图,你将在保存侧。