6

First, my app's setup:

Most of the app's view controllers exist in your standard navigation controller hierarchy, but I also have a second window over the main app window, which hosts a view controller (NotificationVC). If NotificationVC is presenting a notification, it will change the status bar style to contrast with the notifications, but otherwise it defers the style to the main window's root view controller.

My issue is that changes in the main window that would normally trigger a status bar appearance update (pushing, popping or presenting a view controller, or calling -[UIViewController setNeedsStatusBarAppearanceUpdate]) have no effect.

Here's the relevant code from NotificationVC:

@implementation NotificationVC

- (UIStatusBarStyle)preferredStatusBarStyle
{
    if (self.isShowingNotification)
    {
        if (self.notificationView.hasDarkBackground)
        {
            return UIStatusBarStyleLightContent;
        }
        else
        {
            return UIStatusBarStyleDefault;
        }
    }
    else
    {
        return [[UIApplication sharedApplication].delegate window].rootViewController.preferredStatusBarStyle;
    }
}

@end

How can I get the status bar to update from one of the view controllers in the main window?

Note: Manually setting the status bar appearance (-[UIApplication setStatusBarStyle:]) is not an acceptable solution for this app.

Austin
  • 5,625
  • 1
  • 29
  • 43

2 Answers2

1

This seems to be an optimization on UIViewController's part - it won't trigger an update if it isn't in the top window.

I was able to work around the issue by swizzling out UIViewController's implementation of setNeedsStatusAppearanceUpdate for one that is aware of the notification view controller's window.

@interface UIViewController (NotificationWindow)

@end

@implementation UIViewController (NotificationWindow)

+ (void)load
{
    Method original = class_getInstanceMethod([UIViewController class], @selector(setNeedsStatusBarAppearanceUpdate));
    Method swizzled = class_getInstanceMethod([UIViewController class], @selector(swiz_setNeedsStatusBarAppearanceUpdate));
    method_exchangeImplementations(original, swizzled);
}

- (void)swiz_setNeedsStatusBarAppearanceUpdate
{
    UIWindow *topWindow = [UIApplication sharedApplication].windows.lastObject;

    if (![self.view.window isEqual:topWindow] && [topWindow.rootViewController isKindOfClass:[NotificationVC class]])
    {
        [[[UIApplication sharedApplication].windows.lastObject rootViewController] swiz_setNeedsStatusBarAppearanceUpdate];
    }
    else
    {
        [self swiz_setNeedsStatusBarAppearanceUpdate];
    }
}

@end

It's a little hacky — I try to avoid swizzling — but it allows the status bar appearance API to work as-is, without requiring every other view controller to be aware of NotificationVC.

Austin
  • 5,625
  • 1
  • 29
  • 43
-2

Lets say you have 3 view controllers on screen, vc1 vc2 vc3 and they implement preferredStatusBarStyle with a different appearance. In order to get the status bar to take on the appearance set by vc1, something needs to call:

[vc1 setNeedsStatusBarAppearanceUpdate]

Or of course the ViewController can trigger itself as well:

[self setNeedsStatusBarAppearanceUpdate]

When you want to switch to another ViewController's layout, you call setNeedsStatusBarAppearanceUpdate on that view controller.

As an added bonus, you can animate the changes like so:

[UIView animateWithDuration:0.3 animations:^{
    [self setNeedsStatusBarAppearanceUpdate];
}];
Andrew
  • 1,279
  • 2
  • 13
  • 19
  • Problem is - that doesn't work. I stated in my question that `setNeedsStatusBarAppearanceUpdate` has no effect when called from a VC in the main window (which is _not_ the top window). – Austin May 28 '14 at 15:36
  • Oh I'm really sorry about that. I need to read more carefully. Try calling `makeKeyAndVisible` on the `UIWindow` that holds the notification controller – Andrew May 28 '14 at 16:10