3 votes

Comment préserver l'espace occupé par la barre d'état lorsque l'on masque la barre d'état de manière animée ?

J'ai tendance à masquer la barre d'état, animée de la manière suivante.

enter image description here

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

Cependant, je voudrais également préserver l'espace occupé par la barre d'état, de sorte que lorsque la barre d'état apparaît, toute l'application ne soit pas "poussée vers le haut".

Puis-je savoir comment je peux y parvenir ?

Merci.

3voto

aheze Points 6891

Vous pouvez utiliser additionalSafeAreaInsets pour ajouter une hauteur de remplacement, en substituant la barre d'état.

Mais pour les appareils dotés d'une encoche comme l'iPhone 12, l'espace est automatiquement préservé, vous n'avez donc pas besoin d'ajouter de hauteur supplémentaire.

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

Résultat (testé sur iPhone 12, iPhone 8, iPad Pro 4e génération) :

iPhone 12 (encoche)

iPhone 8 (sans encoche)

Show and hide side menu. The button to perform hide and show doesn't jump up when status bar hides.

Show and hide side menu. The button to perform hide and show doesn't jump up when status bar hides.

iPhone 12 + barre de navigation

iPhone 8 + barre de navigation

Show and hide side menu. The button to perform hide and show doesn't jump up when status bar hides, and navigation bar stays where it is.

Show and hide side menu. The button to perform hide and show doesn't jump up when status bar hides, and navigation bar stays where it is.

Repo GitHub de démonstration

0voto

Tarun Tyagi Points 1581

Tout d'abord, il n'est actuellement pas possible de faire UINavigationController se comportent de cette manière. Cependant, vous pouvez envelopper votre UINavigationController dans un contrôleur de vue de conteneur.

Cela vous donnera le contrôle de la gestion de l'espace supérieur à partir duquel l UINavigationController la mise en page de la vue commence. À l'intérieur de cette classe de conteneur, vous pouvez la gérer comme suit

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

}

Maintenant, à partir de votre site d'appel, vous pouvez faire ce qui suit -.

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

}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X