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

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

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就不会被“上推”了

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

谢谢。

【问题讨论】:

    标签: ios swift


    【解决方案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()
                }
            }
        }
    
    }
    

    【讨论】:

      猜你喜欢
      • 2014-10-30
      • 2014-11-29
      • 2011-04-29
      • 2016-02-15
      • 1970-01-01
      相关资源
      最近更新 更多