我按照这个线程来覆盖-preferredStatusBarStyle
,但它没有被调用。我可以更改任何选项以启用它吗?(我在我的项目中使用 XIB。)
25 回答
对于使用 UINavigationController 的任何人:
UINavigationController
不会转发preferredStatusBarStyle
对其子视图控制器的调用。相反,它管理自己的状态 - 它应该在屏幕顶部绘制状态栏所在的位置,因此应该对此负责。因此preferredStatusBarStyle
,在导航控制器中的 VC 中实施将无济于事 - 它们永远不会被调用。
诀窍是UINavigationController
使用什么来决定返回什么UIStatusBarStyleDefault
或UIStatusBarStyleLightContent
。它基于其UINavigationBar.barStyle
. 默认 ( UIBarStyleDefault
) 会导致深色前景UIStatusBarStyleDefault
状态栏。并且UIBarStyleBlack
会给出一个UIStatusBarStyleLightContent
状态栏。
TL;博士:
如果你想UIStatusBarStyleLightContent
使用UINavigationController
:
self.navigationController.navigationBar.barStyle = UIBarStyleBlack;
可能的根本原因
我有同样的问题,并发现它正在发生,因为我没有在我的应用程序窗口中设置根视图控制器。
我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
所以我实际上向 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 现在的默认设置?)
对于仍在为此苦苦挣扎的任何人,这个简单的 swift 扩展应该可以为您解决问题。
extension UINavigationController {
override open var childForStatusBarStyle: UIViewController? {
return self.topViewController
}
}
我的应用程序使用了所有三个: 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
}
}
在 UINavigationControllerpreferredStatusBarStyle
上,不会调用它,因为它topViewController
比self
. 因此,要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
}
}
泰森的答案是正确的将状态栏颜色更改为白色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;
}
除了 serenn 的回答之外,如果您正在呈现一个带有modalPresentationStyle
(例如.overCurrentContext
)的视图控制器,您还应该在新呈现的视图控制器上调用它:
presentedViewController.modalPresentationCapturesStatusBarAppearance = true
不要忘记也覆盖preferredStatusBarStyle
呈现的视图控制器中的。
斯威夫特 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
因为默认情况下它是真的
更复杂的流程要考虑的要点
如果您以模态方式呈现新流程,它将与现有的状态栏样式流程分离。因此,假设您正在呈现 a
NewFlowUIViewController
然后将新的导航或 tabBar 控制器添加到NewFlowUIViewController
,然后添加扩展名NewFlowUIViewController
以管理进一步的视图控制器的状态栏样式。如果您设置modalPresentationStyle不是
fullScreen
在模态呈现时,您必须设置modalPresentationCapturesStatusBarAppearance
为 true 以便呈现的视图控制器必须接收状态栏外观控制。
iOS 13 解决方案
UINavigationController
是UIViewController
(谁知道)的子类!
因此,当呈现嵌入在导航控制器中的视图控制器时,您并没有真正呈现嵌入的视图控制器。您正在展示导航控制器!UINavigationController
, 作为 , 的子类UIViewController
, 继承preferredStatusBarStyle
和childForStatusBarStyle
, 您可以根据需要进行设置。
以下任何一种方法都应该有效:
- 完全退出黑暗模式
- 在您的
info.plist
中,添加以下属性:- 键 -
UIUserInterfaceStyle
(又名“用户界面风格”) - 价值 - 光
- 键 -
- 在您的
覆盖
preferredStatusBarStyle
范围内UINavigationController
preferredStatusBarStyle
( doc ) - 视图控制器的首选状态栏样式子类或扩展
UINavigationController
class MyNavigationController: UINavigationController { override var preferredStatusBarStyle: UIStatusBarStyle { .lightContent } }
或者
extension UINavigationController { open override var preferredStatusBarStyle: UIStatusBarStyle { .lightContent } }
覆盖
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 } }
您可以在上面返回您想要的任何视图控制器。我推荐以下之一:
注意:如果您决定子类UINavigationController
化,请记住通过 IB 中的身份检查器将该类应用于您的导航控制器。
PS 我的代码使用 Swift 5.1 语法
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
该解决方案可能比切换到即将被弃用的行为更好。
@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
}
ViewControllerpreferredStatusBarStyle
将被调用。
就我而言,我不小心将 View/Navigation Controller 呈现为UIModalPresentationStyle.overFullScreen
,这导致preferredStatusBarStyle
没有被调用。将其切换回 后UIModalPresentationStyle.fullScreen
,一切正常。
iOS 7 中的 UIStatusBarStyle
iOS 7 中的状态栏是透明的,它后面的视图是透明的。
状态栏的样式是指其内容的外观。在 iOS 7 中,状态栏内容为深色 ( UIStatusBarStyleDefault
) 或浅色 ( UIStatusBarStyleLightContent
)。两者UIStatusBarStyleBlackTranslucent
和UIStatusBarStyleBlackOpaque
在 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。
如果有人使用导航控制器并希望他们的所有导航控制器都具有黑色样式,您可以在 Swift 3 中像这样为 UINavigationController 编写一个扩展,它将适用于所有导航控制器(而不是将其分配给一个控制器时间)。
extension UINavigationController {
override open func viewDidLoad() {
super.viewDidLoad()
self.navigationBar.barStyle = UIBarStyle.black
}
}
至于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];
Swift 3 iOS 10 解决方案:
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
在 Swift 中用于任何类型的 UIViewController:
在你的AppDelegate
集合中:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
window!.rootViewController = myRootController
return true
}
myRootController
可以是任何类型UIViewController
,例如UITabBarController
或UINavigationController
。
然后,像这样覆盖这个根控制器:
class RootController: UIViewController {
override func preferredStatusBarStyle() -> UIStatusBarStyle {
return .LightContent
}
}
这将改变整个应用程序中状态栏的外观,因为根控制器完全负责状态栏的外观。
请记住将属性设置View controller-based status bar appearance
为 YESInfo.plist
以使其正常工作(这是默认设置)。
大多数答案不包括childViewControllerForStatusBarStyle
方法的良好实现UINavigationController
。根据我的经验,您应该处理诸如透明视图控制器出现在导航控制器上的情况。在这些情况下,您应该将控制权传递给您的模态控制器 ( visibleViewController
),但不是在它消失时。
override var childViewControllerForStatusBarStyle: UIViewController? {
var childViewController = visibleViewController
if let controller = childViewController, controller.isBeingDismissed {
childViewController = topViewController
}
return childViewController?.childViewControllerForStatusBarStyle ?? childViewController
}
这是我解决这个问题的方法。
定义一个名为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
当然,您可以从协议中实现其余的方法(showsStatusBar、animatesStatusBarVisibility、prefferedStatusBarAnimation),并且UIViewController+Upgrade将根据它们提供的值进行适当的自定义。
如果有人用 UISearchController 遇到这个问题。只需创建一个新的 UISearchController 子类,然后将以下代码添加到该类中:
override func preferredStatusBarStyle() -> UIStatusBarStyle {
return .LightContent
}
从 Xcode 11.4 开始,覆盖preferredStatusBarStyle
UINavigationController 扩展中的属性不再起作用,因为它不会被调用。
barStyle
将of设置navigationBar
为.black
确实有效,但是如果您将子视图添加到导航栏,这会增加不必要的副作用,而导航栏可能在明暗模式下具有不同的外观。因为通过将 设置barStyle
为黑色,无论应用程序如何,userInterfaceStyle
嵌入在导航栏中的视图都将始终具有。userInterfaceStyle.dark
userInterfaceStyle
我想出的正确解决方案是在其中添加一个子类UINavigationController
并覆盖preferredStatusBarStyle
。如果您随后将此自定义 UINavigationController 用于您的所有视图,您将处于保存状态。
请注意,使用self.navigationController.navigationBar.barStyle = UIBarStyleBlack;
解决方案时
请务必转到您的 plist 并将“查看基于控制器的状态栏外观”设置为“是”。如果它没有,它将不起作用。
NavigationController 或 TabBarController 是需要提供样式的。这是我解决的方法:https ://stackoverflow.com/a/39072526/242769