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 不生效?

当控制器嵌套在 UINavigationControllerUITabBarController 中时,系统默认问的是最外层容器控制器的状态栏样式,而不是子控制器。

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 修改状态栏背景色。
  • 引入 UIWindowScenestatusBarManager,获取状态栏高度的方式变化:
- (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 不生效

可能原因及解决:

  1. Info.plist 中 UIViewControllerBasedStatusBarAppearance 设为了 NO

    • 解决:改为 YES,或用全局方式 [UIApplication setStatusBarStyle:]
  2. 控制器嵌套在 UINavigationController / UITabBarController 中

    • 解决:创建容器控制器子类,把状态栏控制权交给子控制器(见第四节)。
  3. 没有调用 setNeedsStatusBarAppearanceUpdate

    • 解决:动态修改时必须调用此方法触发更新。
  4. 在 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.plistUIViewControllerBasedStatusBarAppearance 保持默认(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

也可以用 childViewControllerForStatusBarStylechildViewControllerForStatusBarHidden 这两个方法,直接告诉系统"问我的子控制器去",比重写 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 方式。

posted @ 2015-10-29 01:25  Mr.陳  阅读(1778)  评论(0)    收藏  举报