UIViewController's prefersStatusBarHidden not working

Solution 1:

In iOS7, there's actually a new property for UIViewController called modalPresentationCapturesStatusBarAppearance. Apple iOS reference.

Default value is NO.

When you present a view controller by calling the presentViewController:animated:completion: method, status bar appearance control is transferred from the presenting to the presented view controller only if the presented controller’s modalPresentationStyle value is UIModalPresentationFullScreen. By setting this property to YES, you specify the presented view controller controls status bar appearance, even though presented non–fullscreen.

The system ignores this property’s value for a view controller presented fullscreen.

Therefore, for any presentationStyle other than the normal fullscreen (for example: UIModalPresentationCustom), this must be set if you want to capture the status bar. To use, all you have to do is set it to YES on the view controller that's being presented:

toVC.modalPresentationCapturesStatusBarAppearance = YES;

Solution 2:

I'm going to guess (educated, but still a guess) that this is because when you do a presented view controller using a custom transition, in iOS 7, the old view controller is still there. Therefore it probably still gets a say.

You might even put a breakpoint in its prefersStatusBarHidden to see; you'll have to implement it if it isn't implemented. The default is NO, so if it is consulted, that would explain your result.

If I'm right, you would need to implement the old view controller's prefersStatusBarHidden to give two different answers, depending on whether it has a presentedViewController or not.

EDIT I've now confirmed this. It's even worse than I thought; in my testing, the second view controller's prefersStatusBarHidden isn't being called at all. The whole thing is in the hands of the first view controller. This makes sense because, as I said, the first view controller never goes away; with a custom presentation animation, the second view controller is subordinate to the first one, because the second view can hover partially over the first view.

Thus you're going to have to drive the status bar entirely from the first view controller. You can cause its prefersStatusBarHidden to be called by calling [self setNeedsStatusBarAppearanceUpdate]. You'll need to give a different answer depending on the circumstances. This can be a bit tricky. Here's a simple implementation, but it may not cover all the cases:

// ViewController1:

-(void)setHide:(NSNumber*)yn {
    self->hide = [yn boolValue]; // a BOOL ivar
    [self setNeedsStatusBarAppearanceUpdate];
}
-(BOOL)prefersStatusBarHidden {
    return self->hide;
}
- (IBAction)doButton:(id)sender {
    self->hide = YES;
    [self setNeedsStatusBarAppearanceUpdate];
    [self presentViewController:[ViewController2 new] animated:YES completion:nil];
}

// ==========

// ViewController2:

- (IBAction)doButton:(id)sender {
    [self.presentingViewController setValue:NO forKey:@"hide"];
    [self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
}

Solution 3:

If it's not working and your UIViewController is a child in UINavigationController, then this code might be a solution for you.

open override var prefersStatusBarHidden: Bool {
    return topViewController?.prefersStatusBarHidden ?? super.prefersStatusBarHidden
}

Basically the UINavigationController uses it's own prefersStatusBarHidden value, but in my case I wanted to overwrite that by its top view controller's property in hierarchy.