覆盖基于视图控制器的状态栏外观

Overriding view controller based status bar appearance

我有一个基于视图控制器的状态栏外观设置为 YES 的应用程序。我的一些视图有深色内容,一些视图有浅色内容,并且应用程序有一个非常复杂的视图控制器层次结构,但它与子类化和重写适当的方法以及捕获呈现样式等的模态视图完美结合。

但是,我需要一种全局方式来查看顶部的特定项目(在状态栏后面,在我的应用程序范围内),就像个人热点/GarageBand recording/in 在顶部调用等栏。由于栏的背景颜色,我想在显示栏时覆盖状态栏的外观(它可以在应用程序中的任何位置显示,所以我将 UIWindow 子类化并将其显示代码和视图直接放在那里)。该栏在具有浅色内容状态栏的屏幕上完全按照我的要求显示(因为我的栏的文本是白色的,背景是深色的)但在深色内容状态栏上看起来很糟糕(不,我无法更改栏的颜色)。

如何在全局覆盖 "whatever the currently presented view controller is" 的首选状态栏样式(当然,无需遍历所有视图控制器中状态栏方法的所有实例),同时仍然使用基于视图控制器的状态栏外观?我的应用面向 iOS 8.0+。

我以一种非常笨拙(但有效)的方式结束了。它可能不适用于所有情况,但在我的情况下有效。我一直保持视图不变,没有触及任何视图或控制器。

首先,我得到了当前显示的最顶层视图控制器。我使用了 iPhone -- How to find topmost view controller 中的代码并对其进行了一些修改以处理导航控制器和选项卡栏控制器的情况:

+ (UIViewController*) topmostControllerForViewController:(__kindof UIViewController*)topController
{
    while (topController.presentedViewController) {
        topController = topController.presentedViewController;
    }
    if([topController isKindOfClass:[UINavigationController class]]){
        UINavigationController *navController = topController;
        return [self topmostControllerForViewController:navController.visibleViewController];
    }
    if([topController isKindOfClass:[UITabBarController class]]){
        UITabBarController *tabController = topController;
        return [self topmostControllerForViewController:tabController.selectedViewController];
    }
    return topController;
}

+ (UIViewController*) topmostController
{
    __kindof UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController;
    return [self topmostControllerForViewController:topController];
}

然后我创建了一个没有视图的视图控制器(viewnil)。在它的 init 方法中(如果放在 viewDidLoad:not 在第一次调用中工作,因为它在转换过程中被调用并且为时已晚),我添加了以下:

self.modalPresentationCapturesStatusBarAppearance = YES;
self.modalPresentationStyle = UIModalPresentationOverCurrentContext;

该代码允许我的 "dummy" view(less) 控制器处理所有显示上下文,包括状态栏外观以及其他视图控制器在显示时发生的情况。当在当前上下文中呈现时,后面的视图控制器不会从视图层次结构中删除。如果我不这样做,它将被删除并且屏幕将变黑(因为我没有任何视图并且我希望显示之前的视图控制器)。

到目前为止一切顺利。然后,我正常显示了我的栏,但同时,在没有任何视图的情况下模态显示了该视图控制器。因为视图控制器没有任何视图并且在当前上下文中呈现,所以它在视觉上没有以任何方式出现,但是由于它是模态呈现并且虚拟视图控制器被设置为捕获呈现样式,它触发了 iOS 向我的应用询问状态栏样式。我只是在视图控制器方法中设置了我想要的状态栏样式。

有点小问题。当我展示新的视图控制器时,系统在我以前的视图控制器之上添加了一个 UITransitionView。如果有实际视图,它将位于过渡视图之上。转换视图是完全透明的,但它启用了用户交互并捕获了所有触摸事件,这使得我的应用程序在我关闭控制器之前没有响应。我需要我以前的视图控制器来接收触摸事件。我深入挖掘,发现模态呈现添加过渡视图的位置,并在过渡动画完成后呈现视图控制器时将其删除:

for (UIView *view in self.subviews) {
        NSString *className = NSStringFromClass([view class]);
        if([className hasPrefix:@"UIT"] && className.length == 16){
            //this must be UITransitionView, but I'm not using it directly since it may interfere with private API usage and get app rejected by Apple.
            //now, we need to find another transition view inside this and remove it
            for (UIView *innerView in view.subviews) {
                className = NSStringFromClass([innerView class]);
                if([className hasPrefix:@"UIT"] && className.length == 16){
                    //this is the transition view that we need to remove
                    [innerView removeFromSuperview];
                } 
            }
        }
}

由于 UITransitionView 是私有视图类型,我不确定它是否会导致 App Store 出现问题,因此我通过检查首字母 UITransitionView 进行了启发式检查 UIT 并检查 class 名称的长度。它不是防弹的,但它似乎有效并且不太可能 return 误报。

现在一切正常。它很老套,将来可能会崩溃,特别是如果模态表示在引擎盖下发生变化时。但请放心,它有效。