如何在iOS7中使用淡入淡出效果切换状态栏(类似于照片应用程序)?

14

我希望能够在轻击时切换状态栏的可见性,就像照片应用程序中所做的那样。

iOS 7之前,这段代码运行良好:

-(void)setStatusBarIsHidden:(BOOL)statusBarIsHidden {

    _statusBarIsHidden = statusBarIsHidden;

    if (statusBarIsHidden == YES) {

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


    }else{

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

    }

}

但我无法在iOS 7中使其工作。我找到的所有答案都只提供了永久隐藏栏的建议,而没有切换。

然而,既然Photos可以做到,肯定有办法。


可能是重复的问题:无法在iOS7中隐藏状态栏 - Shawn
8个回答

44

在iOS 7或更高版本中,默认情况下,若要隐藏特定视图控制器的状态栏,请执行以下操作:

  1. 如果您要使用模态呈现方式显示要隐藏状态栏的视图控制器,并且modalPresentationStyle不是UIModalPresentationFullScreen,则在呈现控制器之前(例如在-presentViewController:animated:completion-prepareForSegue:(如果您正在使用storyboard)中手动将modalPresentationCapturesStatusBarAppearance设置为YES)。
  2. 在呈现的控制器中覆盖-prefersStatusBarHidden并返回适当的值。
  3. 在呈现的控制器上调用setNeedsStatusBarAppearanceUpdate

如果您想要动画效果,请在动画块中执行第三步:

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

您还可以通过在所呈现的控制器中从-preferredStatusBarUpdateAnimation返回适当的UIStatusBarAnimation值来设置动画的样式。


+eleventyone是指向preferredStatusBarUpdateAnimation的指针。 - xaphod
动画根本不起作用。我应该把它放在哪里?viewWillAppear和viewDidAppear都不起作用。 - Wingzero
哇,你最后一句话“返回适当的UIStatusBarAnimation”非常重要。我添加了一个BOOL来检测视图是否出现,并为BOOL返回不同的样式解决了问题。但我很困惑为什么苹果会选择这种方式。[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleDefault animated:YES];毫不费力地完成了所有操作。 - Wingzero
2
modalPresentationCapturesStatusBarAppearance 为我解决了问题。非常好的答案。 - Jesse Naugher

7

首先,在Info.plist中将View controller-based status bar appearance设置为YES

这个Swift示例演示了如何在按下按钮后使用动画切换状态栏。

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()
        })
    }
}

很棒的答案,简单明了的解决方案,没有任何变通和 hack。 - Alexander Tkachenko

5
我能够简化@Jon的回答,并且仍然获得与iOS 7上的照片应用程序无法区分的行为。看起来当显示时延迟更新不是必要的。
- (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;
}

4
这可能被认为是一种折中的方法,但这是我尝试重现效果的最接近方法。还有一个小问题,当淡出时,你可以看到导航栏从顶部调整大小,虽然微妙,但仍不是完美的淡出。如果有人知道如何解决,请告诉我!
- (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;

        }];


    }

}

你最终解决了导航栏跳动的问题吗? - SAHM
@SAHM 啊,这是一个老问题,我不记得那个项目了。看起来这个页面有很多新的解决方案,希望一些较新的建议能够更好地工作。 - anna

3

实际上,现在没有必要搞乱导航栏框架。您只需使用两个单独的动画块即可实现平滑动画。像这样的东西应该可以很好地工作。

@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;

}

为了与iOS 6兼容,只需确保检查[self respondsToSelector:@selector(setNeedsStatusBarAppearanceUpdate)]


3

这段代码完美运行:

-(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;
            }];

        });

    }

}

1
为了解决导航栏在淡出时向上滑动的问题,您应该添加以下代码:
self.navigationController.navigationBar.frame = CGRectZero;

在以下代码行之前,将其插入到您的“淡入”部分:

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

这是必要的,因为框架相同,设置相同的框架将被忽略,并且无法阻止导航栏滑动。因此,您需要将框架更改为其他内容,然后再将其设置为正确的框架以触发更改。


1

解决此问题的方法取决于您应用程序的plist中“View controller-based status bar appearance”的设置值。

如果您的plist中“View controller-based status bar appearance”为NO,则此代码应该有效:

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

如果“基于视图控制器的状态栏外观”开启,在您的视图控制器中添加此方法:
- (BOOL) prefersStatusBarHidden {
    // I've hardcoded to YES here, but you can return a dynamic value to meet your needs for toggling
    return YES;
}

切换状态时,若您希望根据上述方法的值来更改状态栏的隐藏/显示,您的视图控制器可以调用setNeedsStatusBarAppearanceUpdate方法。


我尝试过了,但它没有做我想要的事情。淡入动画从未发生。当 "prefersStatusBarHidden" 触发时,它隐藏了状态栏,但也更改了边界,导致我的导航栏跳起来。我希望整个 '导航栏 + 状态栏' 区块可以简单地淡入/淡出。 - anna
啊,我没意识到你也在使用导航栏。如果同时调用setNeedsStatusBarAppearanceUpdate方法并使用UINavigationController的setNavigationBarHidden:animated:方法开始动画隐藏导航栏会发生什么?https://developer.apple.com/library/ios/documentation/uikit/reference/UINavigationController_Class/Reference/Reference.html#//apple_ref/occ/instm/UINavigationController/setNavigationBarHidden:animated: - Greg
好的,我已经成功让它们两个都淡出了,但是它仍然改变了边界,因此导航栏在淡出时会向上滑动。有什么想法可以防止这种情况发生吗? - anna
我只是在淡化它,但我猜“prefersStatusBarHidden”会触发视图边界的变化。 - anna
对不起,我不确定;抱歉。 - Greg
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接