动态隐藏状态栏时如何保留状态栏占用的空间?

Posted

技术标签:

【中文标题】动态隐藏状态栏时如何保留状态栏占用的空间?【英文标题】:How to preserve space occupied by status bar when hiding status bar animately? 【发布时间】:2021-05-29 08:55:49 【问题描述】:

我倾向于隐藏状态栏,动画方式如下。

var statusBarHidden: Bool = false 
    didSet 
        UIView.animate(withDuration: Constants.config_shortAnimTime)  () -> Void in
            self.setNeedsStatusBarAppearanceUpdate()
        
    


override var prefersStatusBarHidden: Bool 
    return statusBarHidden


override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation
    return .slide


extension ViewController: SideMenuNavigationControllerDelegate 
    func sideMenuWillAppear(menu: SideMenuNavigationController, animated: Bool) 
        statusBarHidden = true
    

    func sideMenuDidAppear(menu: SideMenuNavigationController, animated: Bool) 
    

    func sideMenuWillDisappear(menu: SideMenuNavigationController, animated: Bool) 
    

    func sideMenuDidDisappear(menu: SideMenuNavigationController, animated: Bool) 
        statusBarHidden = false
    

不过,我也想保留状态栏占用的空间,这样当状态栏出现时,整个app就不会被“上推”了

我可以知道我怎样才能做到这一点吗?

谢谢。

【问题讨论】:

【参考方案1】:

您可以使用additionalSafeAreaInsets添加占位符高度,替换状态栏。

但对于像 iPhone 12 这样带有凹口的设备,空间会自动保留,因此您无需添加任何额外的高度。

class ViewController: UIViewController 

    var statusBarHidden: Bool = false /// no more computed property, otherwise reading safe area would be too late
    override var prefersStatusBarHidden: Bool 
        return statusBarHidden
    

    override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation
        return .slide
    
    
    @IBAction func showButtonPressed(_ sender: Any) 
        statusBarHidden.toggle()
        if statusBarHidden 
            sideMenuWillAppear()
         else 
            sideMenuWillDisappear()
        
    
    
    lazy var overlayViewController: UIViewController = 
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        return storyboard.instantiateViewController(withIdentifier: "OverlayViewController")
    ()
    
    var additionalHeight: CGFloat 
        if view.window?.safeAreaInsets.top ?? 0 > 20  /// is iPhone X or other device with notch
            return 0 /// add 0 height
         else 
            /// the height of the status bar
            return view.window?.windowScene?.statusBarManager?.statusBarFrame.height ?? 0.0
        
    


extension ViewController 
    
    /// add placeholder height to substitute status bar
    func addAdditionalHeight(_ add: Bool) 
        if add 
            if let navigationController = self.navigationController 
                /// set insets of navigation controller if you're using navigation controller
                navigationController.additionalSafeAreaInsets.top = additionalHeight
             else 
                /// set insets of self if not using navigation controller
                self.additionalSafeAreaInsets.top = additionalHeight
            
         else 
            if let navigationController = self.navigationController 
                /// set insets of navigation controller if you're using navigation controller
                navigationController.additionalSafeAreaInsets.top = 0
             else 
                /// set insets of self if not using navigation controller
                self.additionalSafeAreaInsets.top = 0
            
        
    
    
    func sideMenuWillAppear() 
        
        addChild(overlayViewController)
        view.addSubview(overlayViewController.view)
        overlayViewController.view.frame = view.bounds
        overlayViewController.view.frame.origin.x = -400
        overlayViewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        overlayViewController.didMove(toParent: self)
        
        addAdditionalHeight(true) /// add placeholder height
        
        UIView.animate(withDuration: 1) 
            self.overlayViewController.view.frame.origin.x = -100
            self.setNeedsStatusBarAppearanceUpdate() /// hide status bar
        
    

    func sideMenuDidAppear() 

    func sideMenuWillDisappear() 
        
        addAdditionalHeight(false) /// remove placeholder height
        
        UIView.animate(withDuration: 1) 
            self.overlayViewController.view.frame.origin.x = -400
            self.setNeedsStatusBarAppearanceUpdate() /// show status bar
         completion:  _ in
            self.overlayViewController.willMove(toParent: nil)
            self.overlayViewController.view.removeFromSuperview()
            self.overlayViewController.removeFromParent()
        
    

    func sideMenuDidDisappear() 

结果(在 iPhone 12、iPhone 8、iPad Pro 第 4 代测试):

iPhone 12 (notch) iPhone 8 (no notch)
iPhone 12 + navigation bar iPhone 8 + navigation bar

Demo GitHub repo

【讨论】:

感谢您的提示。我试着听从你的建议。但是,顶部空间仍然受到动画的影响,如 - imgur.com/a/UW4aq7a 所示,你知道我在代码中做错了什么吗? - gist.github.com/yccheok/084439dee440e3ce9200c104950b7529 谢谢。 @CheokYanCheng 我已经添加了演示 GitHub 存储库的链接。在您的项目中,您是否在其他任何地方调用过setNeedsStatusBarAppearanceUpdate 同时搜索layoutIfNeeded。这可能会影响动画。 谢谢。在用你的演示代码测试之后,我知道我的代码有什么问题 - gist.github.com/yccheok/b32fd2d321c18818037a62e06b1bbab9 似乎当我们调整顶部时,prefersStatusBarHidden 也会被触发。因此,我们需要首先改变 bool 变量。非常感谢【参考方案2】:

首先,目前不可能使UINavigationController 以这种方式运行。但是,您可以将 UINavigationController 实例包装在容器视图控制器中。

这将使您能够控制从UINavigationController 视图布局开始的顶部空间的管理。在这个容器类中,你可以像下面这样管理它 -

class ContainerViewController: UIViewController 

    private lazy var statusBarBackgroundView: UIView = 
        let view = UIView(frame: .zero)
        view.backgroundColor = .clear
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    ()

    private lazy var statusBarBackgroundViewHeightConstraint: NSLayoutConstraint = 
        statusBarBackgroundView.heightAnchor.constraint(equalToConstant: 0)
    ()
    
    var statusBarHeight: CGFloat 
        if #available(ios 13.0, *) 
            guard let statusBarMananger = self.view.window?.windowScene?.statusBarManager 
            else  return 0 
            return statusBarMananger.statusBarFrame.height
         else 
            return UIApplication.shared.statusBarFrame.height
        
    

    var statusBarHidden: Bool = false 
        didSet 
            self.statusBarBackgroundViewHeightConstraint.constant = self.statusBarHidden ? self.lastKnownStatusBarHeight : 0
            self.view.layoutIfNeeded()
        
    

    private var lastKnownStatusBarHeight: CGFloat = 0

    override func viewDidLoad() 
        super.viewDidLoad()
        
        let topView = self.statusBarBackgroundView
        self.view.addSubview(topView)
        NSLayoutConstraint.activate([
            topView.topAnchor.constraint(equalTo: self.view.topAnchor),
            topView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
            statusBarBackgroundViewHeightConstraint,
            topView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
        ])
    
    
    override func viewDidLayoutSubviews() 
        super.viewDidLayoutSubviews()
        
        let height = self.statusBarHeight
        if height > 0 
            self.lastKnownStatusBarHeight = height
        
    

    func setUpNavigationController(_ navCtrl: UINavigationController) 
        self.addChild(navCtrl)
        navCtrl.didMove(toParent: self)
        self.view.addSubview(navCtrl.view)
    
        navCtrl.view.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            navCtrl.view.topAnchor.constraint(equalTo: statusBarBackgroundView.bottomAnchor),
            navCtrl.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
            navCtrl.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
            navCtrl.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
        ])
    
        self.view.layoutIfNeeded()
    


现在从您的呼叫站点,您可以执行以下操作 -

class ViewController: UIViewController 
    
    var statusBarHidden: Bool = false 
        didSet 
            UIView.animate(withDuration: Constants.config_shortAnimTime)  () -> Void in
                
                /// Forward the call to ContainerViewController to act on this update
                (self.navigationController?.parent as? ContainerViewController)?.statusBarHidden = self.statusBarHidden
                
                /// Keep doing whatever you are doing now
                self.setNeedsStatusBarAppearanceUpdate()
            
        
    


【讨论】:

以上是关于动态隐藏状态栏时如何保留状态栏占用的空间?的主要内容,如果未能解决你的问题,请参考以下文章

iOS 7隐藏导航栏时如何更改状态栏的颜色?

隐藏状态栏时如何强制windowSoftInputMode调整大小

隐藏导航栏时隐藏状态栏 - SWIFT iOS8

隐藏导航栏时的假状态栏颜色

隐藏状态栏时,我的导航栏在 iOS7 中向上移动

隐藏状态栏时,iOS 11 搜索栏没有顶部填充