50

I am trying to make the status bar for one of my view controllers to be hidden (when displayed modally). When I'm presenting the view controller, the status bar is is to be hidden and then returned when dismissed.

I have added the following code to the presented view controller

- (BOOL)prefersStatusBarHidden
{
    return YES;
}

I have also set the keys in the Info.plist file to the following:

<key>UIViewControllerBasedStatusBarAppearance</key>
<true/>

From my understanding this should be all that is required to make this work.

I am also using a custom Animation Controller to do the presenting which conforms to the UIViewControllerAnimatedTransitioning protocol. In the animateTransition: implementation I have tried to manually call prefersStatusBarHidden, followed by setNeedsStatusBarAppearanceUpdate to ensure the call is being made, but the status bar remains.

Any ideas why this is happening would be appreciated. I have searched StackOverflow, but it appears no one has had this issue, all accepted answers refer to calling setNeedsStatusBarAppearanceUpdate, which I am already doing.

EDIT - The code below now seems to WORK as desired

- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext
{
    if (self.isPresenting) {
        UIView *containerView = [transitionContext containerView];

        UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
        UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];

        toViewController.view.frame = containerView.frame;

        [containerView addSubview:toViewController.view];

        // Ask the presented controller whether to display the status bar
        [toViewController setNeedsStatusBarAppearanceUpdate];

        [UIView animateWithDuration:1.0f delay:0.0f options:UIViewAnimationOptionCurveEaseIn animations:^{
            toViewController.view.alpha = 1.0f;
            fromViewController.view.alpha = 0.0f;
        } completion:^(BOOL finished) {
            [transitionContext completeTransition:YES];
        }];
    }
    else {
        // do the reverse
        UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
        UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];

        [UIView animateWithDuration:1.0f delay:0.0f options:UIViewAnimationOptionCurveEaseIn animations:^{
            toViewController.view.alpha = 1.0f;
            fromViewController.view.alpha = 0.0f;
        } completion:^(BOOL finished) {
            [transitionContext completeTransition:YES];
            // Once dismissed - ask the presenting controller if the status bar should be presented
            [toViewController setNeedsStatusBarAppearanceUpdate];
        }];
    }
}

....

// PresentingController.m
- (BOOL)prefersStatusBarHidden
{
    if (self.presentedViewController) {
        return YES;
    }
    return NO;
}

// PresentedController.m
- (BOOL)prefersStatusBarHidden
{
    return YES;
}
Ravi Gautam
  • 960
  • 2
  • 9
  • 20
Taz
  • 1,203
  • 1
  • 11
  • 21
  • 1
    Add a breakpoint in the prefersStatusBarHidden to check if its getting called. – Arbitur May 12 '14 at 17:55
  • It's definitely getting called, the breakpoint is hit. – Taz May 12 '14 at 17:57
  • Have you tried to remove the key? because they shouldnt be needed. – Arbitur May 12 '14 at 17:59
  • Yep, I have tried that. Interestingly, I have just tried to present the controller without the custom transition controller (iOS native modal) and it seems to remove the status bar. But I need to get it working with this custom animation controller. – Taz May 12 '14 at 18:04
  • Hmm maybe post some code how the custom animation is working? – Arbitur May 12 '14 at 18:25
  • I agree with @Arbitur. Basically, you have now _proved_ that the custom transition is what's messing things up. Which, by the way, is excellent; that's how debugging / thinking about these things is done. – matt May 12 '14 at 19:06
  • @Arbitur - added the custom transition code as requested. It's very basic. Can't see much wrong with it. – Taz May 13 '14 at 10:07

4 Answers4

130

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;
Community
  • 1
  • 1
David Liu
  • 9,426
  • 5
  • 40
  • 63
  • 7
    Just noticed that this is not working if the modal presentation style is 'over current context' toVC.modalPresentationStyle = UIModalPresentationStyle.OverCurrentContext – valR Mar 24 '15 at 00:25
  • 1
    it works too for UIModalPresentationStyle.OverCurrentContext – Wilson Dec 10 '16 at 20:58
  • I got mad til I found this tip :-))) – Karsten Dec 13 '16 at 11:54
  • Thank you, using presentationStype = .overCurrentContext in iOS8 is my problem. I have spent 2 hours searching for solution. In iOS9+ , I don't have to set this modalPresentationCapturesStatusBarAppearance to true anymore. – Daron Tancharoen Jan 08 '17 at 14:24
  • You save my day, I was using it with Google BottomSheet, it works fine. You need to combine it with `prefersStatusBarHidden = true` – Jerry Okafor Jun 19 '19 at 10:40
17

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];
}
matt
  • 515,959
  • 87
  • 875
  • 1,141
  • Yes, I have a feeling that this is the issue too. I need to have a check through the controller hierarchy as the presenting controller is returning YES if a modal is presented. So, in theory, it should work. – Taz May 13 '14 at 09:23
  • Thanks @matt, your comments have been very helpful. I've now edited my code in the question to something that seems to be working for me. The workaround I used was to manually call the controller prefersStatusBarHidden methods during the custom animation. I'll accept this answer as it explains the problem accurately. – Taz May 13 '14 at 20:34
  • Except that you can cut the lines that look like this: `[toViewController prefersStatusBarHidden];` - they are wrong and do nothing. – matt May 13 '14 at 21:13
  • If the `[toViewController prefersStatusBarHidden];` calls are removed from the dismissal code, the status bar is not returned once the presentedViewController is dismissed. – Taz May 13 '14 at 21:30
  • 2
    I find it hard to understand how that can be. `prefersStatusBarHidden` is supposed to be called by the system. It returns a BOOL, which you are not even capturing (you are just throwing it away); and you are calling it behind the system's back, so the system learns nothing. It is the call to `setNeedsStatusBarAppearanceUpdate` that's important, as it causes the _system_ to call `prefersStatusBarHidden` and thus retrieve its result afresh. – matt May 13 '14 at 21:42
  • Ahh yes, my bad. I, for some reason, read that as removing both `prefersStatusBarHidden` and `setNeedsStatusBarAppearanceUpdate`. Updated! Long, long day. Time for some sleep ... :) – Taz May 13 '14 at 21:46
  • A good day's work. Your question was an important one and I learned something; thanks for asking it. – matt May 13 '14 at 21:54
  • Can the first view controller forward the request to the second by implementing `[UIViewController -childViewControllerForStatusBarHidden]`? – Aaron Brager Jul 14 '14 at 02:41
  • @AaronBrager your presented view controller is not your child – matt Jul 14 '14 at 04:59
  • @matt I know, but I was curious if forwarding the responsibility this way anyway would work. I guess I can try it out :) – Aaron Brager Jul 14 '14 at 05:19
  • @matt There's actually no need for any of this. There's a property that just needs to be set to YES. See my answer. – David Liu Aug 15 '14 at 21:33
  • @DavidLiu Cool! If this works, the OP should accept your answer instead of mine (and then I can delete mine). – matt Aug 16 '14 at 02:05
1

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.

Konrad Siemczyk
  • 186
  • 2
  • 9
0

You may add this to info.plist

"View controller-based status bar appearance" and set the value to "No"

zuyao88
  • 64
  • 8
  • Setting this property to YES in combination with overriding `prefersStatusBarHidden` in the specific view controller did hide the status bar in only this view controller for me. – thetrutz May 06 '19 at 21:08