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:
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):
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:
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)
}
}
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:
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.