0
votes

Trying to hide both TabBar and StatusBar simultaneously and inside the same animation block, I came across an incomprehensible layout behavior. Starting to hide TabBar in the usual way with tabbar item 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()
    }
}

So far so good:

enter image description here

Now let's add animation for 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()
    }
}

Now StatusBar is sliding, bur TabBar froze (I don't know why):

enter image description here

Any attempts to update layout using layoutIfNeeded(), setNeedsLayout() etc. were unsuccessful. Now let's swap animations for TabBar and 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()
}

Both are sliding now, but TabBar started to jump at the begining of animation:

enter image description here

I found that when adding directives for StatusBar to an animation block, a ViewDidLayoutSubviews() starts to be called additionally. Actually, you can fix the initial position of TabBar inside 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

The disadvantage of this method is that TabBar can twitch during the movement, depending on the speed of movement and other factors.

Another way (without using ViewDidLayoutSubviews()) is contrary to logic but works in practice. Namely, you can put one animation in a completion block of another one:

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

Following the logic, we have two consecutive animations. And TabBar animation should begin after StatusBar animation ends. However, in practice:

enter image description here

The disadvantage of this method is that if you want to reverse the animation (for example, the user tapped the screen while TabBar is moving), the variable animator1.isRunning will be false, although physically the StatusBar will still move around the screen (I also don't know why).

Looking forward to reading your comments, suggestions, explanations.

1

1 Answers

0
votes

The logic is that setNeedsStatusBarAppearanceUpdate() is animated asyncroniusly. I.e. StatusBar animation ends immediately after start and then runs in a thread that cannot be paused or reversed. It’s a pity that iOS SDK doesn't provide StatusBar animation control. How to prevent the effect of setNeedsStatusBarAppearanceUpdate() animation on the layout, I still do not know.