277

我按照这个线程来覆盖-preferredStatusBarStyle,但它没有被调用。我可以更改任何选项以启用它吗?(我在我的项目中使用 XIB。)

4

25 回答 25

1057

对于使用 UINavigationController 的任何人:

UINavigationController不会转发preferredStatusBarStyle对其子视图控制器的调用。相反,它管理自己的状态 - 它应该在屏幕顶部绘制状态栏所在的位置,因此应该对此负责。因此preferredStatusBarStyle,在导航控制器中的 VC 中实施将无济于事 - 它们永远不会被调用。

诀窍是UINavigationController使用什么来决定返回什么UIStatusBarStyleDefaultUIStatusBarStyleLightContent。它基于其UINavigationBar.barStyle. 默认 ( UIBarStyleDefault) 会导致深色前景UIStatusBarStyleDefault状态栏。并且UIBarStyleBlack会给出一个UIStatusBarStyleLightContent状态栏。

TL;博士:

如果你想UIStatusBarStyleLightContent使用UINavigationController

self.navigationController.navigationBar.barStyle = UIBarStyleBlack;
于 2013-10-22T09:16:13.780 回答
122

可能的根本原因

我有同样的问题,并发现它正在发生,因为我没有在我的应用程序窗口中设置根视图控制器。

UIViewController在其中实现的preferredStatusBarStyle被用于 a UITabBarController,它控制屏幕上视图的外观。

当我将根视图控制器设置为指向 thisUITabBarController时,状态栏更改开始正常工作,正如预期的那样(并且该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

于 2013-09-26T15:48:52.547 回答
103

所以我实际上向 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
}

最后,确保您的应用 plist 没有“基于视图控制器的状态栏外观”设置为 NO。删除该行或将其设置为 YES(我相信这是 iOS 7 现在的默认设置?)

于 2013-10-31T16:31:38.443 回答
86

对于仍在为此苦苦挣扎的任何人,这个简单的 swift 扩展应该可以为您解决问题。

extension UINavigationController {
    override open var childForStatusBarStyle: UIViewController? {
        return self.topViewController
    }
}
于 2017-02-17T15:21:48.057 回答
23

我的应用程序使用了所有三个: UINavigationController, UISplitViewController, UITabBarController,因此这些似乎都控制了状态栏,并且会导致preferedStatusBarStyle他们的孩子不会被调用。要覆盖此行为,您可以创建一个扩展,就像其他答案提到的那样。这是 Swift 4 中所有三个的扩展。希望 Apple 对这类东西更清楚。

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
    }
}
于 2018-02-01T04:15:53.900 回答
18

在 UINavigationControllerpreferredStatusBarStyle上,不会调用它,因为它topViewControllerself. 因此,要preferredStatusBarStyle在 UINavigationController 上调用,您需要更改其childViewControllerForStatusBarStyle.

推荐

在你的类中覆盖你的 UINavigationController:

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

不推荐的替代品

要为所有 UINavigationController 执行此操作,您可以在扩展中覆盖(警告:它会影响 UIDocumentPickerViewController、UIImagePickerController 等),但根据 Swift 文档,您可能不应该这样做

extension UINavigationController {
    open override var preferredStatusBarStyle: UIStatusBarStyle {
        return .lightContent
    }
    open override var childViewControllerForStatusBarStyle: UIViewController? {
        return nil
    }
}
于 2016-09-02T07:14:05.517 回答
15

泰森的答案是正确的将状态栏颜色更改为白色UINavigationController

如果有人想通过编写代码来完成相同的结果,请AppDelegate使用下面的代码并将其写入AppDelegate's didFinishLaunchingWithOptions方法中。

并且不要忘记在 .plist 文件中设置为UIViewControllerBasedStatusBarAppearanceYES否则更改将不会反映。

代码

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

     return YES;
}
于 2015-05-23T11:33:47.920 回答
15

除了 serenn 的回答之外,如果您正在呈现一个带有modalPresentationStyle(例如.overCurrentContext)的视图控制器,您还应该在新呈现的视图控制器上调用它:

presentedViewController.modalPresentationCapturesStatusBarAppearance = true

不要忘记也覆盖preferredStatusBarStyle呈现的视图控制器中的。

于 2018-09-18T14:32:22.687 回答
12

斯威夫特 4.2 及以上

所选答案中所述,根本原因是检查您的窗口根视图控制器对象。

您的流程结构的可能案例

  • 自定义 UIViewController 对象是窗口根视图控制器

    您的窗口根视图控制器是一个 UIViewController 对象,它会根据您的应用程序流进一步添加或删除导航控制器或 tabController。

    如果您的应用程序在没有选项卡的导航堆栈上具有预登录流程,并且在具有选项卡的登录后流程并且可能每个选项卡进一步包含导航控制器,则通常使用这种流程。

  • TabBarController 对象是窗口根视图控制器

    这是窗口根视图控制器是 tabBarController 的流程,可能每个选项卡都进一步包含导航控制器。

  • NavigationController 对象是窗口根视图控制器

    这是窗口根视图控制器是 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
    }
}
  • 您不需要UIViewControllerBasedStatusBarAppearance键入,info.plist因为默认情况下它是真的

更复杂的流程要考虑的要点

  • 如果您以模态方式呈现新流程,它将与现有的状态栏样式流程分离。因此,假设您正在呈现 aNewFlowUIViewController然后将新的导航或 tabBar 控制器添加到NewFlowUIViewController,然后添加扩展名NewFlowUIViewController以管理进一步的视图控制器的状态栏样式。

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

于 2019-06-28T06:03:52.710 回答
11

iOS 13 解决方案

UINavigationControllerUIViewController(谁知道)的子类!

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

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

  1. 完全退出黑暗模式
    • 在您的info.plist中,添加以下属性:
      • 键 - UIUserInterfaceStyle(又名“用户界面风格”)
      • 价值 - 光
  2. 覆盖preferredStatusBarStyle范围内UINavigationController

    • preferredStatusBarStyle( doc ) - 视图控制器的首选状态栏样式
    • 子类或扩展UINavigationController

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

      或者

      extension UINavigationController {
          open override var preferredStatusBarStyle: UIStatusBarStyle {
              .lightContent
          }
      }
      
  3. 覆盖childForStatusBarStyle范围内UINavigationController

    • childForStatusBarStyle( doc ) - 当系统需要视图控制器用于确定状态栏样式时调用
    • 根据苹果的文档,

      “如果您的容器视图控制器从其子视图控制器之一派生其状态栏样式,[覆盖此属性]并返回该子视图控制器。如果您返回 nil 或不覆盖此方法,则使用 self 的状态栏样式. 如果此方法的返回值发生变化,请调用 setNeedsStatusBarAppearanceUpdate() 方法。"

    • 换句话说,如果您在此处不执行解决方案 3,系统将回退到上面的解决方案 2。
    • 子类或扩展UINavigationController

      class MyNavigationController: UINavigationController {
          override var childForStatusBarStyle: UIViewController? {
              topViewController
          }
      }
      

      或者

      extension UINavigationController {    
          open override var childForStatusBarStyle: UIViewController? {
              topViewController
          }
      }
      
    • 您可以在上面返回您想要的任何视图控制器。我推荐以下之一:

      • topViewController(of UINavigationController) ( doc ) - 位于导航堆栈顶部的视图控制器
      • visibleViewController(of UINavigationController) ( doc ) - 与导航界面中当前可见视图相关联的视图控制器(提示:这可以包括“以模态方式呈现在导航控制器本身之上的视图控制器”)

注意:如果您决定子类UINavigationController化,请记住通过 IB 中的身份检查器将该类应用于您的导航控制器。

PS 我的代码使用 Swift 5.1 语法

于 2019-10-23T03:24:41.243 回答
9

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

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

于 2013-10-15T05:02:23.980 回答
7

@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
}
于 2016-10-26T15:36:14.213 回答
6

如果您的 viewController 在 UINavigationController 下。

子类 UINavigationController 并添加

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

ViewControllerpreferredStatusBarStyle将被调用。

于 2016-10-29T11:45:21.463 回答
4

就我而言,我不小心将 View/Navigation Controller 呈现为UIModalPresentationStyle.overFullScreen,这导致preferredStatusBarStyle没有被调用。将其切换回 后UIModalPresentationStyle.fullScreen,一切正常。

于 2019-09-30T23:31:17.923 回答
4

iOS 7 中的 UIStatusBarStyle

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

状态栏的样式是指其内容的外观。在 iOS 7 中,状态栏内容为深色 ( UIStatusBarStyleDefault) 或浅色 ( UIStatusBarStyleLightContent)。两者UIStatusBarStyleBlackTranslucentUIStatusBarStyleBlackOpaque在 iOS 7.0 中已弃用。改为使用UIStatusBarStyleLightContent

如何改变UIStatusBarStyle

如果状态栏下方是导航栏,则会调整状态栏样式以匹配导航栏样式(UINavigationBar.barStyle):

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

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

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

- (UIStatusBarStyle)preferredStatusBarStyle
  {
      return UIStatusBarStyleLightContent;
  }

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

如果您希望选择退出此行为并使用该-[UIApplication statusBarStyle]方法设置状态栏样式,请将UIViewControllerBasedStatusBarAppearance键添加到应用程序的Info.plist文件中并将其值设为 NO。

于 2015-10-27T15:39:22.900 回答
3

如果有人使用导航控制器并希望他们的所有导航控制器都具有黑色样式,您可以在 Swift 3 中像这样为 UINavigationController 编写一个扩展,它将适用于所有导航控制器(而不是将其分配给一个控制器时间)。

extension UINavigationController {

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

        self.navigationBar.barStyle = UIBarStyle.black
    }

}
于 2017-01-11T13:54:01.953 回答
2

至于iOS 13.4,不会调用category中的preferredStatusBarStyle方法,swizzling似乎是唯一的选择,不需要使用子类。UINavigationController

例子:

类别标题:

@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];
于 2020-04-16T14:08:21.387 回答
1

Swift 3 iOS 10 解决方案:

override var preferredStatusBarStyle: UIStatusBarStyle {
    return .lightContent
 }
于 2016-10-05T11:15:00.757 回答
1

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

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

请记住将属性设置View controller-based status bar appearance为 YESInfo.plist以使其正常工作(这是默认设置)。

于 2016-07-22T09:02:32.737 回答
1

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

override var childViewControllerForStatusBarStyle: UIViewController? {
  var childViewController = visibleViewController
  if let controller = childViewController, controller.isBeingDismissed {
    childViewController = topViewController
  }
  return childViewController?.childViewControllerForStatusBarStyle ?? childViewController
}
于 2018-10-03T13:38:23.417 回答
0

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

定义一个名为AGViewControllerAppearance的协议。

AGViewControllerAppearance.h

#import <Foundation/Foundation.h>

@protocol AGViewControllerAppearance <NSObject>

@optional

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

@end

在UIViewController上定义一个名为Upgrade的类别。

UIViewController+升级.h

#import <UIKit/UIKit.h>

@interface UIViewController (Upgrade)

//
//  Replacements
//

- (void)upgradedViewWillAppear:(BOOL)animated;

@end

UIViewController+升级.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),并且UIViewController+Upgrade将根据它们提供的值进行适当的自定义。

于 2014-01-11T23:25:55.740 回答
0

如果有人用 UISearchController 遇到这个问题。只需创建一个新的 UISearchController 子类,然后将以下代码添加到该类中:

override func preferredStatusBarStyle() -> UIStatusBarStyle {
    return .LightContent
}
于 2015-07-19T12:14:15.417 回答
0

从 Xcode 11.4 开始,覆盖preferredStatusBarStyleUINavigationController 扩展中的属性不再起作用,因为它不会被调用。

barStyle将of设置navigationBar.black确实有效,但是如果您将子视图添加到导航栏,这会增加不必要的副作用,而导航栏可能在明暗模式下具有不同的外观。因为通过将 设置barStyle为黑色,无论应用程序如何,userInterfaceStyle嵌入在导航栏中的视图都将始终具有。userInterfaceStyle.darkuserInterfaceStyle

我想出的正确解决方案是在其中添加一个子类UINavigationController并覆盖preferredStatusBarStyle。如果您随后将此自定义 UINavigationController 用于您的所有视图,您将处于保存状态。

于 2020-06-08T10:19:33.930 回答
0

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

请务必转到您的 plist 并将“查看基于控制器的状态栏外观”设置为“是”。如果它没有,它将不起作用。

于 2016-10-21T17:20:19.717 回答
-1

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

于 2016-08-22T06:25:10.700 回答