iOS开发基础71-应用中的 StatusBar 详解
iOS StatusBar 状态栏深度解析:从基础用法到适配与常见问题
StatusBar(状态栏)是 iOS 应用顶部显示时间、电池、网络等系统状态的区域。看似简单,但在实际开发中经常遇到"改了不生效"、"导航控制器嵌套后失控"、"iPhone X 适配"等问题。本文将系统梳理 StatusBar 的所有知识点,包括基础用法、自定义、不同 iOS 版本变化、导航控制器嵌套处理、深色模式适配,以及常见问题排查。
一、StatusBar 基本概念
1. StatusBar 是什么
StatusBar 是 iOS 系统在屏幕顶部提供的状态信息显示区域,通常包含:
- 时间
- 网络状态(5G/4G/Wi-Fi 等)
- Wi-Fi 信号强度
- 运营商名称(部分机型/状态下隐藏)
- 电池电量
- 闹钟状态、定位服务、通话中等图标
2. StatusBar 的高度
| 设备 | 状态栏高度 |
|---|---|
| iPhone 非全面屏(iPhone 8 及以前) | 20 pt |
| iPhone 全面屏(iPhone X 及以后) | 44 pt |
| iPad | 20 pt(普通状态)/ 24 pt(多任务分屏时) |
注意:iOS 14 及以后,iPhone 全面屏在通话、录音、热点等状态下,状态栏左上角会出现绿色/橙色圆点,但高度不变。
3. 全局控制 vs 控制器控制
iOS 7 引入了 UIViewControllerBasedStatusBarAppearance 配置,决定了状态栏由谁控制:
| 模式 | 配置值 | 控制方式 | 适用场景 |
|---|---|---|---|
| 全局控制 | UIViewControllerBasedStatusBarAppearance = NO |
通过 UIApplication 统一设置 |
整个 App 状态栏样式统一 |
| 控制器控制(默认) | UIViewControllerBasedStatusBarAppearance = YES |
每个控制器重写 preferredStatusBarStyle 等方法 |
不同页面状态栏样式不同 |
Info.plist 配置:
<key>UIViewControllerBasedStatusBarAppearance</key> <false/> <!-- NO 表示全局控制;不写或 YES 表示控制器控制 -->
二、StatusBar 的基础操作
1. 显示和隐藏 StatusBar
方式一:全局控制(Info.plist 设为 NO)
在 AppDelegate 中统一设置:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 隐藏状态栏(带动画)
[application setStatusBarHidden:YES withAnimation:UIStatusBarAnimationSlide];
return YES;
}
动画类型:
UIStatusBarAnimationNone:无动画UIStatusBarAnimationFade:淡入淡出UIStatusBarAnimationSlide:滑动
方式二:控制器控制(默认,Info.plist 设为 YES 或不写)
在需要隐藏状态栏的控制器中重写:
- (BOOL)prefersStatusBarHidden {
return YES; // YES 隐藏,NO 显示
}
动态切换显示/隐藏
// 调用此方法触发 prefersStatusBarHidden 重新执行
[self setNeedsStatusBarAppearanceUpdate];
完整示例:
@interface ViewController ()
@property (nonatomic, assign) BOOL statusBarHidden;
@end
@implementation ViewController
- (BOOL)prefersStatusBarHidden {
return self.statusBarHidden;
}
- (void)toggleStatusBar {
self.statusBarHidden = !self.statusBarHidden;
[UIView animateWithDuration:0.3 animations:^{
[self setNeedsStatusBarAppearanceUpdate];
}];
}
@end
2. 修改 StatusBar 样式(文字颜色)
UIStatusBarStyle 枚举值:
| 枚举值 | 效果 | 系统版本 |
|---|---|---|
UIStatusBarStyleDefault |
黑色文字(自动适应深色模式) | iOS 7+ |
UIStatusBarStyleLightContent |
白色文字 | iOS 7+ |
UIStatusBarStyleDarkContent |
黑色文字 | iOS 13+ |
iOS 13+ 注意:
UIStatusBarStyleDefault在浅色模式下是黑色文字,深色模式下自动变为白色文字。如果需要强制黑色文字,用UIStatusBarStyleDarkContent。
方式一:全局控制
// AppDelegate 中
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[application setStatusBarStyle:UIStatusBarStyleLightContent animated:YES];
return YES;
}
方式二:控制器控制
- (UIStatusBarStyle)preferredStatusBarStyle {
return UIStatusBarStyleLightContent;
}
动态修改样式
- (UIStatusBarStyle)preferredStatusBarStyle {
return self.isDarkBackground ? UIStatusBarStyleLightContent : UIStatusBarStyleDarkContent;
}
- (void)updateStatusBarStyle {
[self setNeedsStatusBarAppearanceUpdate];
}
3. 修改 StatusBar 动画样式
- (UIStatusBarAnimation)preferredStatusBarUpdateAnimation {
return UIStatusBarAnimationSlide; // None / Fade / Slide
}
三、StatusBar 的自定义
1. 自定义 StatusBar 背景颜色
iOS 没有直接修改状态栏背景色的 API,常见做法是在状态栏区域添加一个自定义 View。
方法一:在当前控制器 View 上添加(推荐)
- (void)viewDidLoad {
[super viewDidLoad];
// 获取状态栏高度
CGFloat statusBarHeight = [self statusBarHeight];
// 创建状态栏背景 View
UIView *statusBarBg = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, statusBarHeight)];
statusBarBg.backgroundColor = [UIColor colorWithRed:0.2 green:0.5 blue:0.9 alpha:1.0];
statusBarBg.autoresizingMask = UIViewAutoresizingFlexibleWidth;
[self.view addSubview:statusBarBg];
}
- (CGFloat)statusBarHeight {
if (@available(iOS 13.0, *)) {
NSSet *scenes = [UIApplication sharedApplication].connectedScenes;
UIWindowScene *windowScene = (UIWindowScene *)[scenes anyObject];
return windowScene.statusBarManager.statusBarFrame.size.height;
} else {
return [UIApplication sharedApplication].statusBarFrame.size.height;
}
}
方法二:通过 KeyWindow 添加(全局生效)
- (void)setStatusBarBackgroundColor:(UIColor *)color {
if (@available(iOS 13.0, *)) {
// iOS 13+ 不能直接访问 statusBarWindow,用 windowScene 的 statusBarManager
// 推荐在每个控制器添加背景 View,或用全局 UIWindow 覆盖
UIWindow *keyWindow = [self keyWindow];
UIView *statusBarBg = [keyWindow viewWithTag:99999];
if (!statusBarBg) {
statusBarBg = [[UIView alloc] init];
statusBarBg.tag = 99999;
[keyWindow addSubview:statusBarBg];
}
CGFloat statusBarHeight = [self statusBarHeight];
statusBarBg.frame = CGRectMake(0, 0, keyWindow.bounds.size.width, statusBarHeight);
statusBarBg.backgroundColor = color;
} else {
// iOS 12 及以下,可以直接访问 statusBarWindow
UIView *statusBar = [[[UIApplication sharedApplication] valueForKey:@"statusBarWindow"] valueForKey:@"statusBar"];
if ([statusBar respondsToSelector:@selector(setBackgroundColor:)]) {
statusBar.backgroundColor = color;
}
}
}
- (UIWindow *)keyWindow {
if (@available(iOS 13.0, *)) {
for (UIWindowScene *scene in [UIApplication sharedApplication].connectedScenes) {
if (scene.activationState == UISceneActivationStateForegroundActive) {
for (UIWindow *window in scene.windows) {
if (window.isKeyWindow) return window;
}
}
}
}
return [UIApplication sharedApplication].keyWindow;
}
重要:iOS 13 以后,
statusBarWindow这个私有 API 被移除了,不能再用 KVC 访问。推荐用方法一(每个控制器添加背景 View),这是最稳定、最兼容的做法。
2. 完全自定义 StatusBar 内容
如果需要完全自定义状态栏内容(如显示自定义文字),需要先隐藏系统状态栏,再在状态栏区域添加自定义 View:
- (BOOL)prefersStatusBarHidden {
return YES; // 隐藏系统状态栏
}
- (void)viewDidLoad {
[super viewDidLoad];
CGFloat statusBarHeight = [self statusBarHeight];
// 自定义状态栏容器
UIView *customStatusBar = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, statusBarHeight)];
customStatusBar.backgroundColor = [UIColor blackColor];
customStatusBar.autoresizingMask = UIViewAutoresizingFlexibleWidth;
[self.view addSubview:customStatusBar];
// 自定义文字
UILabel *titleLabel = [[UILabel alloc] init];
titleLabel.text = @"自定义状态栏";
titleLabel.textColor = [UIColor whiteColor];
titleLabel.font = [UIFont systemFontOfSize:14 weight:UIFontWeightSemibold];
titleLabel.textAlignment = NSTextAlignmentCenter;
titleLabel.frame = CGRectMake(0, statusBarHeight - 20, self.view.bounds.size.width, 20);
titleLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth;
[customStatusBar addSubview:titleLabel];
}
注意:隐藏系统状态栏后,时间、电池、信号等系统信息也会消失。苹果不推荐在普通页面隐藏状态栏,仅在全屏视频、游戏等场景使用。
四、导航控制器(UINavigationController)嵌套时的状态栏管理
1. 问题:为什么在子控制器改了 preferredStatusBarStyle 不生效?
当控制器嵌套在 UINavigationController 或 UITabBarController 中时,系统默认问的是最外层容器控制器的状态栏样式,而不是子控制器。
2. 解决方案一:让导航控制器把控制权交给子控制器
创建 UINavigationController 子类,重写状态栏相关方法:
@interface BaseNavigationController : UINavigationController
@end
@implementation BaseNavigationController
// 把状态栏样式的控制权交给顶部的子控制器
- (UIStatusBarStyle)preferredStatusBarStyle {
return [self.topViewController preferredStatusBarStyle];
}
// 把状态栏隐藏的控制权交给顶部的子控制器
- (BOOL)prefersStatusBarHidden {
return [self.topViewController prefersStatusBarHidden];
}
// 把动画样式的控制权交给顶部的子控制器
- (UIStatusBarAnimation)preferredStatusBarUpdateAnimation {
return [self.topViewController preferredStatusBarUpdateAnimation];
}
@end
然后在项目中使用 BaseNavigationController 代替系统的 UINavigationController,这样子控制器的 preferredStatusBarStyle 就会生效。
3. 解决方案二:直接在导航控制器上设置
如果不想创建子类,也可以在导航控制器上直接设置(全局统一):
@implementation UINavigationController (StatusBar)
- (UIStatusBarStyle)preferredStatusBarStyle {
return UIStatusBarStyleLightContent;
}
@end
4. UITabBarController 嵌套时
如果还有 UITabBarController,同样需要让 TabBarController 把控制权交给选中的子控制器:
@interface BaseTabBarController : UITabBarController
@end
@implementation BaseTabBarController
- (UIStatusBarStyle)preferredStatusBarStyle {
return [self.selectedViewController preferredStatusBarStyle];
}
- (BOOL)prefersStatusBarHidden {
return [self.selectedViewController prefersStatusBarHidden];
}
@end
嵌套层级:如果是
UITabBarController → UINavigationController → UIViewController,需要每一层都把控制权往下传,最终才能到达最内层的UIViewController。
五、不同 iOS 版本的变化与适配
1. iOS 7 之前
- 状态栏由应用全局控制,通过
UIApplication设置样式和显隐。 - 状态栏不覆盖在应用 UI 之上,应用从状态栏下方开始布局。
2. iOS 7 ~ iOS 12
- 引入
UIViewControllerBasedStatusBarAppearance,支持控制器级别的状态栏控制。 - 状态栏覆盖在应用 UI 之上(透明),应用需要自己处理顶部布局。
- 可以通过 KVC 访问
statusBarWindow修改背景色(iOS 13 后被移除)。
3. iOS 11 ~ iPhone X 全面屏
- iPhone X 开始状态栏高度从 20pt 变为 44pt。
- 引入 Safe Area(安全区域),推荐用
view.safeAreaInsets.top获取顶部安全距离,而不是硬编码状态栏高度。
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
CGFloat topInset = self.view.safeAreaInsets.top;
NSLog(@"顶部安全距离(含状态栏): %f", topInset);
}
4. iOS 13+
- 引入
UIStatusBarStyleDarkContent,支持深色模式。 UIStatusBarStyleDefault会自动适应深色模式(浅色黑字,深色白字)。- 移除了
statusBarWindow私有 API,不能再用 KVC 修改状态栏背景色。 - 引入
UIWindowScene和statusBarManager,获取状态栏高度的方式变化:
- (CGFloat)statusBarHeight {
CGFloat height = 0;
if (@available(iOS 13.0, *)) {
for (UIWindowScene *scene in [UIApplication sharedApplication].connectedScenes) {
if (scene.activationState == UISceneActivationStateForegroundActive) {
height = scene.statusBarManager.statusBarFrame.size.height;
break;
}
}
} else {
height = [UIApplication sharedApplication].statusBarFrame.size.height;
}
return height;
}
5. iOS 14+
- 引入"状态栏指示器"(绿色/橙色圆点),表示 App 正在使用麦克风/摄像头/位置。
- 全面屏 iPhone 状态栏高度仍为 44pt,但左上角会出现小圆点。
六、深色模式适配
1. 自动适应深色模式
使用 UIStatusBarStyleDefault,系统会自动根据当前界面风格切换状态栏文字颜色:
- (UIStatusBarStyle)preferredStatusBarStyle {
return UIStatusBarStyleDefault; // 浅色模式黑字,深色模式白字
}
2. 根据页面背景手动适配
如果页面背景色不随系统深色模式变化(如固定的深色背景页面),需要手动返回对应样式:
- (UIStatusBarStyle)preferredStatusBarStyle {
// 固定深色背景页面,永远用白色状态栏
return UIStatusBarStyleLightContent;
}
3. 监听深色模式变化
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
[super traitCollectionDidChange:previousTraitCollection];
// 界面风格变化时,更新状态栏
if (@available(iOS 13.0, *)) {
if ([self.traitCollection hasDifferentColorAppearanceComparedToTraitCollection:previousTraitCollection]) {
[self setNeedsStatusBarAppearanceUpdate];
}
}
}
七、常见问题排查
问题 1:修改 preferredStatusBarStyle 不生效
可能原因及解决:
-
Info.plist 中 UIViewControllerBasedStatusBarAppearance 设为了 NO
- 解决:改为 YES,或用全局方式
[UIApplication setStatusBarStyle:]。
- 解决:改为 YES,或用全局方式
-
控制器嵌套在 UINavigationController / UITabBarController 中
- 解决:创建容器控制器子类,把状态栏控制权交给子控制器(见第四节)。
-
没有调用 setNeedsStatusBarAppearanceUpdate
- 解决:动态修改时必须调用此方法触发更新。
-
在 viewDidLoad 中调用了 setNeedsStatusBarAppearanceUpdate,但时机太早
- 解决:在
viewDidAppear:或之后调用,或在viewWillAppear:中调用。
- 解决:在
问题 2:iPhone X 及以上机型,自定义状态栏背景高度不对
原因:硬编码了 20pt,全面屏是 44pt。
解决:用 Safe Area 或动态获取状态栏高度:
// 推荐:用 Safe Area
CGFloat topInset = self.view.safeAreaInsets.top;
// 或动态获取
CGFloat statusBarHeight = [self statusBarHeight]; // 见第五节的方法
问题 3:iOS 13 以后,用 KVC 修改状态栏背景色崩溃或不生效
原因:iOS 13 移除了 statusBarWindow 私有 API。
解决:改用在状态栏区域添加自定义 View 的方式(见第三节方法一),这是最稳定的做法。
问题 4:隐藏状态栏后,页面布局上移
原因:隐藏状态栏后,系统不会自动调整视图布局,视图仍从屏幕顶部(0,0)开始。
解决:
- 如果用 Auto Layout + Safe Area,隐藏状态栏后 Safe Area 会自动变化,布局会自动调整。
- 如果用手动 frame,需要在
prefersStatusBarHidden变化后手动调整视图 frame。
问题 5:启动页(LaunchScreen)状态栏样式不对
解决:在 Info.plist 中设置 UIStatusBarStyle(全局启动时的样式):
<key>UIStatusBarStyle</key>
<string>UIStatusBarStyleLightContent</string>
或在 Xcode 项目设置 → General → Deployment Info → Status Bar Style 中设置。
八、最佳实践
1. 推荐用控制器控制模式
Info.plist中UIViewControllerBasedStatusBarAppearance保持默认(YES)。- 每个控制器根据自己的背景色返回合适的状态栏样式。
- 深色背景页面用
UIStatusBarStyleLightContent,浅色背景页面用UIStatusBarStyleDefault(自动适应深色模式)。
2. 统一管理导航控制器
创建 BaseNavigationController,把状态栏控制权交给顶部子控制器,一劳永逸:
@implementation BaseNavigationController
- (UIStatusBarStyle)preferredStatusBarStyle {
return [self.topViewController preferredStatusBarStyle];
}
- (BOOL)prefersStatusBarHidden {
return [self.topViewController prefersStatusBarHidden];
}
- (UIStatusBarAnimation)preferredStatusBarUpdateAnimation {
return [self.topViewController preferredStatusBarUpdateAnimation];
}
- (UIViewController *)childViewControllerForStatusBarStyle {
return self.topViewController;
}
- (UIViewController *)childViewControllerForStatusBarHidden {
return self.topViewController;
}
@end
也可以用
childViewControllerForStatusBarStyle和childViewControllerForStatusBarHidden这两个方法,直接告诉系统"问我的子控制器去",比重写 preferredStatusBarStyle 更简洁。
3. 自定义状态栏背景用 Safe Area
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
CGFloat topInset = self.view.safeAreaInsets.top;
self.statusBarBgView.frame = CGRectMake(0, 0, self.view.bounds.size.width, topInset);
}
4. 仅在必要场景隐藏状态栏
- 全屏视频播放、游戏、图片浏览等场景可以隐藏状态栏。
- 普通内容页面不建议隐藏状态栏,用户需要看时间和电量。
九、Swift 版本对照
1. 控制器控制状态栏样式
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
override var prefersStatusBarHidden: Bool {
return false
}
// 动态更新
setNeedsStatusBarAppearanceUpdate()
2. 导航控制器传递控制权
class BaseNavigationController: UINavigationController {
override var preferredStatusBarStyle: UIStatusBarStyle {
return topViewController?.preferredStatusBarStyle ?? .default
}
override var prefersStatusBarHidden: Bool {
return topViewController?.prefersStatusBarHidden ?? false
}
override var childForStatusBarStyle: UIViewController? {
return topViewController
}
override var childForStatusBarHidden: UIViewController? {
return topViewController
}
}
3. 获取状态栏高度
func statusBarHeight() -> CGFloat {
if #available(iOS 13.0, *) {
let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene
return scene?.statusBarManager?.statusBarFrame.height ?? 0
} else {
return UIApplication.shared.statusBarFrame.height
}
}
总结
- StatusBar 两种控制模式:全局控制(Info.plist 设 NO,用 UIApplication 设置)和控制器控制(默认,重写 preferredStatusBarStyle)。推荐用控制器控制。
- 三个核心方法:
preferredStatusBarStyle(样式/文字颜色)、prefersStatusBarHidden(显隐)、preferredStatusBarUpdateAnimation(动画)。动态修改必须调用setNeedsStatusBarAppearanceUpdate。 - 嵌套问题:UINavigationController / UITabBarController 会拦截状态栏控制权,需要创建子类把控制权传递给子控制器(用 childViewControllerForStatusBarStyle 或重写方法)。
- 自定义背景色:iOS 13 后不能用 KVC 访问 statusBarWindow,推荐在状态栏区域添加自定义 View,高度用 Safe Area 或动态获取。
- 版本适配:iOS 13+ 有 DarkContent 样式和深色模式自动适应;iPhone X 后状态栏 44pt,用 Safe Area 适配;iOS 13+ 获取状态栏高度用 statusBarManager。
- 常见问题:改了不生效 → 检查 Info.plist 配置和容器控制器嵌套;高度不对 → 用 Safe Area 不要硬编码;iOS 13 背景色崩溃 → 改用自定义 View 方式。

浙公网安备 33010602011771号