anna
anna

Reputation: 2713

How to toggle status bar with a fade effect in iOS7 (like Photos app)?

I want to toggle the visibility of the status bar on tap, just like it does in the Photos app.

Prior to iOS 7, this code worked well:

-(void)setStatusBarIsHidden:(BOOL)statusBarIsHidden {

    _statusBarIsHidden = statusBarIsHidden;

    if (statusBarIsHidden == YES) {

        [[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:UIStatusBarAnimationFade];


    }else{

        [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationFade];

    }

}

But I can't get it to work in iOS 7. All the answers that I found only offer suggestions for permanently hiding the bar but not toggling.

Yet, there must be a way since Photos does it.

Upvotes: 14

Views: 16886

Answers (8)

followben
followben

Reputation: 9197

By default on iOS 7 or above, to hide the status bar for a specific view controller, do the following:

  1. if the view controller you want to hide the status bar with is being presented modally and the modalPresentationStyle is not UIModalPresentationFullScreen, manually set modalPresentationCapturesStatusBarAppearance to YES on the presented controller before it is presented (e.g. in -presentViewController:animated:completion or -prepareForSegue: if you're using storyboards)
  2. override -prefersStatusBarHidden in the presented controller and return an appropriate value
  3. call setNeedsStatusBarAppearanceUpdate on the presented controller

If you want to animate it's appearance or disappearance, do step three within an animation block:

[UIView animateWithDuration:0.33 animations:^{
    [self setNeedsStatusBarAppearanceUpdate];
}];

You can also set the style of animation by returning an appropriate UIStatusBarAnimation value from -preferredStatusBarUpdateAnimation in the presented controller.

Upvotes: 44

coco
coco

Reputation: 4970

First set View controller-based status bar appearance in Info.plist to YES

This Swift Example shows how to toggle the StatusBar with an Animation, after pressing a Button.

import UIKit

class ToggleStatusBarViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func prefersStatusBarHidden() -> Bool {
        return !UIApplication.sharedApplication().statusBarHidden
    }

    override func preferredStatusBarUpdateAnimation() -> UIStatusBarAnimation {
        return UIStatusBarAnimation.Slide
    }

    @IBAction func toggleStatusBar(sender: UIButton) {
        UIView.animateWithDuration(0.5,
            animations: {
                self.setNeedsStatusBarAppearanceUpdate()
        })
    }
}

Upvotes: 7

Alexander Dvornikov
Alexander Dvornikov

Reputation: 1084

Actually there is now need to mess with navigation bar frames. You can achieve smooth animation just by using 2 separate animation blocks. Something like this should work just fine.

@property (nonatomic, assign) BOOL controlsShouldBeHidden;

...

- (void)setControlsHidden:(BOOL)hidden animated:(BOOL)animated {

    if (self.controlsShouldBeHidden == hidden) {
        return;
    }

    self.controlsShouldBeHidden = hidden;

    NSTimeInterval duration = animated ? 0.3 : 0.0;

    [UIView animateWithDuration:duration animations:^(void) {

        [self setNeedsStatusBarAppearanceUpdate];

    }];

    [UIView animateWithDuration:duration animations:^(void) {

        CGFloat alpha = hidden ? 0 : 1;
        [self.navigationController.navigationBar setAlpha:alpha];

    }];

}

- (BOOL)prefersStatusBarHidden {

    return self.controlsShouldBeHidden;

}

For compatibility with iOS 6 just make sure to check [self respondsToSelector:@selector(setNeedsStatusBarAppearanceUpdate)]

Upvotes: 3

Justin Anderson
Justin Anderson

Reputation: 2130

I was able to simplify @Jon's answer and still get behavior indistinguishable from the Photos app on iOS 7. It looks like the delayed update when showing isn't necessary.

- (IBAction)toggleUI:(id)sender {
    self.hidesUI = !self.hidesUI;

    CGRect barFrame = self.navigationController.navigationBar.frame;

    CGFloat alpha = (self.hidesUI) ? 0.0 : 1.0;
    [UIView animateWithDuration:0.33 animations:^{
        [self setNeedsStatusBarAppearanceUpdate];
        self.navigationController.navigationBar.alpha = alpha;
    }];

    self.navigationController.navigationBar.frame = CGRectZero;
    self.navigationController.navigationBar.frame = barFrame;
}

- (BOOL)prefersStatusBarHidden {
    return self.hidesUI;
}

Upvotes: 5

Jon
Jon

Reputation: 31

This code works perfectly fine:

-(void)setControlsAreHidden:(BOOL)controlsAreHidden {

    if (_controlsAreHidden == controlsAreHidden)
        return;

    _controlsAreHidden = controlsAreHidden;


    UINavigationBar * navigationBar = self.navigationController.navigationBar;

    if (controlsAreHidden == YES) {

        // fade out
        //

        CGRect barFrame = self.navigationController.navigationBar.frame;

        [UIView animateWithDuration:0.3 animations:^ {
            [self setNeedsStatusBarAppearanceUpdate];
            self.navigationController.navigationBar.alpha = 0;
        }];

        self.navigationController.navigationBar.frame = CGRectZero;
        self.navigationController.navigationBar.frame = CGRectMake(0, 20, barFrame.size.width, 44);

    } else {

        // fade in
        //
        [UIView animateWithDuration:UINavigationControllerHideShowBarDuration animations:^ {
            [self setNeedsStatusBarAppearanceUpdate];
        }];

        double delayInSeconds = 0.01;
        dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
        dispatch_after(popTime, dispatch_get_main_queue(), ^(void){

            [self.navigationController setNavigationBarHidden:NO animated:NO];
            navigationBar.alpha = 0;
            [UIView animateWithDuration:UINavigationControllerHideShowBarDuration animations:^ {
                navigationBar.alpha = 1;
            }];

        });

    }

}

Upvotes: 3

Bond007
Bond007

Reputation: 51

To correct this issue with navigation bar sliding up when fading, you should add the following code:

self.navigationController.navigationBar.frame = CGRectZero;

into your "fade in" section before the following code line:

self.navigationController.navigationBar.frame = CGRectMake(0, 20, barFrame.size.width, 64);

This is necessary because the frame is the same and setting the same frame will be ignored and will not stop the navigation bar from sliding. Therefore you need to change the frame to something different and then set it again to the correct frame to trigger the change.

Upvotes: 1

anna
anna

Reputation: 2713

This might be considered a bit of a hack but it's the closest I've come to reproducing the effect. There's still one minor issue. When fading out, you can see the navigation bar being resized from the top. It's subtle enough but still not a perfect fade. If anyone knows how to fix it, let me know!

- (BOOL)prefersStatusBarHidden {

    if (_controlsAreHidden == YES)
        return YES;
    else
        return NO;
}

- (UIStatusBarAnimation)preferredStatusBarUpdateAnimation {

    return UIStatusBarAnimationFade;
}

-(void)setControlsAreHidden:(BOOL)controlsAreHidden {

    _controlsAreHidden = controlsAreHidden;

    if (controlsAreHidden == YES) {

        // fade out
        //

        CGRect barFrame = self.navigationController.navigationBar.frame;

        [UIView animateWithDuration:0.3 animations:^ {


            [self setNeedsStatusBarAppearanceUpdate];

            self.navigationController.navigationBar.alpha = 0;



        }];


        self.navigationController.navigationBar.frame = CGRectMake(0, 20, barFrame.size.width, 44);



    }else{


        // fade in
        //

        CGRect barFrame = self.navigationController.navigationBar.frame;

        self.navigationController.navigationBar.frame = CGRectMake(0, 20, barFrame.size.width, 64);

        [UIView animateWithDuration:0.3 animations:^ {

            [self setNeedsStatusBarAppearanceUpdate];

            self.navigationController.navigationBar.alpha = 1;

        }];


    }

}

Upvotes: 4

Greg
Greg

Reputation: 33650

The way to resolve this depends on the value of the "View controller-based status bar appearance" setting in your app's plist.

If "View controller-based status bar appearance" is NO in your plist, then this code should work:

[[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:UIStatusBarAnimationFade];

If "View controller-based status bar appearance" is on, in your view controllers, add this method:

- (BOOL) prefersStatusBarHidden {
    // I've hardcoded to YES here, but you can return a dynamic value to meet your needs for toggling
    return YES;
}

For toggling, when you want to change whether the status bar is hidden/shown based on the value of the above method, your view controller can call the setNeedsStatusBarAppearanceUpdate method.

Upvotes: 1

Related Questions