UIViewPropertyAnimator: скрыть одновременно TabBar и StatusBar (iOS 13) - PullRequest
0 голосов
/ 09 апреля 2020

Пытаясь скрыть и TabBar, и StatusBar одновременно и внутри одного и того же блока анимации, я натолкнулся на непонятное поведение макета. Начинаем скрывать TabBar обычным способом с viewcontroller элемента табуляции:

import UIKit

class TestViewController: UIViewController { 

    var mainViewController: UITabBarController {
        get {
            return UIApplication.shared.windows.first {$0.rootViewController != nil}?.rootViewController as! UITabBarController
        }
    }

    var offset: CGFloat!

    override func viewDidLoad() {
        super.viewDidLoad()

        offset = mainViewController.tabBar.frame.height
    }

    @IBAction func HideMe(_ sender: Any) {

        let tabBar = self.mainViewController.tabBar
        let animator = UIViewPropertyAnimator(duration: 1, curve: .linear) {
            tabBar.frame = tabBar.frame.offsetBy(dx: 0, dy: self.offset)
        }
        animator.startAnimation()
    }
}

Пока все хорошо:

enter image description here

Теперь давайте добавить анимацию для StatusBar:

import UIKit

class TestViewController: UIViewController {

    var mainViewController: UITabBarController {
        get {
            return UIApplication.shared.windows.first {$0.rootViewController != nil}?.rootViewController as! UITabBarController
        }
    }

    var isTabBarHidden = false {
        didSet(newValue) {
            setNeedsStatusBarAppearanceUpdate()
        }
    }

    override var prefersStatusBarHidden: Bool {
        get {
            return isTabBarHidden
        }
    }

    override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
        get {
            return .slide
        }
    }

    var offset: CGFloat!

    override func viewDidLoad() {
        super.viewDidLoad()

        offset = mainViewController.tabBar.frame.height
    }

    @IBAction func HideMe(_ sender: Any) {

        let tabBar = self.mainViewController.tabBar
        let animator = UIViewPropertyAnimator(duration: 1, curve: .linear) {
            tabBar.frame = tabBar.frame.offsetBy(dx: 0, dy: self.offset)
            self.isTabBarHidden = true
        }
        animator.startAnimation()
    }
}

Теперь StatusBar скользит, но TabBar застыл (я не знаю почему):

enter image description here

Любые попытки обновить макет с помощью layoutIfNeeded (), setNeedsLayout () et c. были неудачными. Теперь давайте поменяем анимацию для TabBar и StatusBar:

@IBAction func HideMe(_ sender: Any) {

    let tabBar = self.mainViewController.tabBar
    let animator = UIViewPropertyAnimator(duration: 1, curve: .linear) {
        self.isTabBarHidden = true
        tabBar.frame = tabBar.frame.offsetBy(dx: 0, dy: self.offset)
    }
    animator.startAnimation()
}

Теперь оба скользят, но TabBar начал прыгать в начале анимации:

enter image description here

Я обнаружил, что при добавлении директив для StatusBar в блок анимации ViewDidLayoutSubviews () начинает вызываться дополнительно. На самом деле вы можете исправить начальную позицию TabBar внутри ViewDidLayoutSubviews ():

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    if isTabBarHidden {
        let tabBar = self.mainViewController.tabBar
        tabBar.frame = tabBar.frame.offsetBy(dx: 0, dy: self.offset)
    }
}

enter image description here

Недостаток этого метода заключается в том, что TabBar может дергаться во время движение, в зависимости от скорости движения и других факторов.

Другой способ (без использования ViewDidLayoutSubviews ()) противоречит логи c, но работает на практике. А именно, вы можете поместить одну анимацию в блок завершения другого:

@IBAction func HideMe(_ sender: Any) {

    let tabBar = self.mainViewController.tabBar

    let animator1 = UIViewPropertyAnimator(duration: 1, curve: .linear) {
        self.isTabBarHidden = !self.isTabBarHidden
    }
    animator1.addCompletion({_ in
        let animator2 = UIViewPropertyAnimator(duration: 1, curve: .linear) {
            tabBar.frame = tabBar.frame.offsetBy(dx: 0, dy: self.offset)
        }
        animator2.startAnimation()
    })
    animator1.startAnimation()
}

Следуя логике c, мы имеем две последовательные анимации. А анимация TabBar должна начинаться после завершения анимации StatusBar. Однако на практике:

enter image description here

Недостаток этого метода заключается в том, что если вы хотите повернуть анимацию вспять (например, пользователь коснулся экрана во время TabBar перемещается), переменная animator1.isRunning будет иметь значение false, хотя физически StatusBar будет по-прежнему перемещаться по экрану (я также не знаю, почему).

Ждем чтения Ваши комментарии, предложения, объяснения.

1 Ответ

0 голосов
/ 09 апреля 2020

Лог c заключается в том, что setNeedsStatusBarAppearanceUpdate () анимируется асинхронно. Т.е. анимация StatusBar заканчивается сразу после запуска, а затем запускается в потоке, который не может быть приостановлен или реверсирован. Жаль, что iOS SDK не обеспечивает управление анимацией StatusBar. Как предотвратить влияние setNeedsStatusBarAppearanceUpdate () анимации на макет, я до сих пор не знаю.

...