So I have a button that is connected to a IBAction. When I press the button I want to hide the tab bar in my iOS app with a animation. This [self setTabBarHidden:hidde
Swift Version:
@IBAction func tap(sender: AnyObject) {
setTabBarVisible(!tabBarIsVisible(), animated: true, completion: {_ in })
}
// pass a param to describe the state change, an animated flag and a completion block matching UIView animations completion
func setTabBarVisible(visible: Bool, animated: Bool, completion:(Bool)->Void) {
// bail if the current state matches the desired state
if (tabBarIsVisible() == visible) {
return completion(true)
}
// get a frame calculation ready
let height = tabBarController!.tabBar.frame.size.height
let offsetY = (visible ? -height : height)
// zero duration means no animation
let duration = (animated ? 0.3 : 0.0)
UIView.animateWithDuration(duration, animations: {
let frame = self.tabBarController!.tabBar.frame
self.tabBarController!.tabBar.frame = CGRectOffset(frame, 0, offsetY);
}, completion:completion)
}
func tabBarIsVisible() -> Bool {
return tabBarController!.tabBar.frame.origin.y < CGRectGetMaxY(view.frame)
}
This wrks for me:
[self.tabBar setHidden:YES];
where self is the view controller, tabBar is the id for the tabBar.
tried in swift 3.0 / iOS10 / Xcode 8:
self.tabBarController?.tabBar.isHidden = true
I set it when my controller is shown: (and Hide when back, after navigation)
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.tabBarController?.tabBar.isHidden = false
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.tabBarController?.tabBar.isHidden = true
}
BTW: better to have a flag to save if shown or not, as other vents can eventually trigger hide/show
[Swift4.2]
Just created an extension for UITabBarController
:
import UIKit
extension UITabBarController {
func setTabBarHidden(_ isHidden: Bool, animated: Bool, completion: (() -> Void)? = nil ) {
if (tabBar.isHidden == isHidden) {
completion?()
}
if !isHidden {
tabBar.isHidden = false
}
let height = tabBar.frame.size.height
let offsetY = view.frame.height - (isHidden ? 0 : height)
let duration = (animated ? 0.25 : 0.0)
let frame = CGRect(origin: CGPoint(x: tabBar.frame.minX, y: offsetY), size: tabBar.frame.size)
UIView.animate(withDuration: duration, animations: {
self.tabBar.frame = frame
}) { _ in
self.tabBar.isHidden = isHidden
completion?()
}
}
}
I went through the previous posts, so I came out with the solution below as subclass of UITabBarController
Main points are:
selectedIndex
Below the subclass TabBarController
:
class TabBarController: UITabBarController {
//MARK: Properties
private(set) var isTabVisible:Bool = true
private var visibleTabBarFrame:CGRect = .zero
private var hiddenTabBarFrame:CGRect = .zero
override var selectedIndex: Int {
didSet { self.updateTabBarFrames() }
}
//MARK: View lifecycle
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
NotificationCenter.default.addObserver(self, selector: #selector(appWillEnterForeground(_:)), name: UIApplication.willEnterForegroundNotification, object: nil)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.calculateTabBarFrames()
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { (_) in }) { (_) in
// when orientation changes, the tab bar frame changes, so we need to update it to the expected state
self.calculateTabBarFrames()
self.updateTabBarFrames()
}
}
@objc private func appWillEnterForeground(_ notification:Notification){
self.updateTabBarFrames()
}
//MARK: Private
/// Calculates the frames of the tab bar and the expected bounds of the shown view controllers
private func calculateTabBarFrames() {
self.visibleTabBarFrame = self.tabBar.frame
self.hiddenTabBarFrame = CGRect(x: self.visibleTabBarFrame.origin.x, y: self.visibleTabBarFrame.origin.y + self.visibleTabBarFrame.height, width: self.visibleTabBarFrame.width, height: self.visibleTabBarFrame.height)
}
/// Updates the tab bar and shown view controller frames based on the current expected tab bar visibility
/// - Parameter tabIndex: if provided, it will update the view frame of the view controller for this tab bar index
private func updateTabBarFrames(tabIndex:Int? = nil) {
self.tabBar.frame = self.isTabVisible ? self.visibleTabBarFrame : self.hiddenTabBarFrame
if let vc = self.viewControllers?[tabIndex ?? self.selectedIndex] {
vc.additionalSafeAreaInsets.bottom = self.isTabVisible ? 0.0 : -(self.visibleTabBarFrame.height - self.view.safeAreaInsets.bottom)
}
self.view.layoutIfNeeded()
}
//MARK: Public
/// Show/Hide the tab bar
/// - Parameters:
/// - show: whether to show or hide the tab bar
/// - animated: whether the show/hide should be animated or not
func showTabBar(_ show:Bool, animated:Bool = true) {
guard show != self.isTabVisible else { return }
self.isTabVisible = show
guard animated else {
self.tabBar.alpha = show ? 1.0 : 0.0
self.updateTabBarFrames()
return
}
UIView.animate(withDuration: 0.25, delay: 0.0, options: [.beginFromCurrentState,.curveEaseInOut], animations: {
self.tabBar.alpha = show ? 1.0 : 0.0
self.updateTabBarFrames()
}) { (_) in }
}
}
extension TabBarController: UITabBarControllerDelegate {
override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
if let tabIndex = self.tabBar.items?.firstIndex(of: item) {
self.updateTabBarFrames(tabIndex: tabIndex)
}
}
}
Sample usage from within a shown view controller:
// hide the tab bar animated (default)
(self.tabBarController as? TabBarController)?.showTabBar(false)
// hide the tab bar without animation
(self.tabBarController as? TabBarController)?.showTabBar(false, animated:false)
Sample output iPhone 11
Sample output iPhone 8
EDIT :
viewControllers
array, you may want to make sure that the navigation controller topViewController
has the property extendedLayoutIncludesOpaqueBars
set to true
(you can set this directly from the Storyboard). This should resolve the problemHope it helps someone :)
My previous answer does not longer work on iOS14. I played with manipulating the frames of the different views, but it seams that the new implementation of the UITabBarController and UITabBar on iOS14 do some magic under the covers which makes this approach no longer working.
I therefore switch to the approach that I hide the UITabBar by setting its alpha to zero and then I manipulate the bottom constraint (that you must pass in when calling the function) to bring the view's content down. This does however, mean that you must have such a constraint and the extension is more bound to your view then the previous approach.
Make sure that the view you are displaying has clipToBounds = false otherwise you will just get a black area where the UITabBar once was!
Here is the code of my UITabBarController.extensions.swift:
import Foundation
extension UITabBarController {
private struct AssociatedKeys {
// Declare a global var to produce a unique address as the assoc object handle
static var orgConstraintConstant: UInt8 = 0
static var orgTabBarAlpha : UInt8 = 1
}
var orgConstraintConstant: CGFloat? {
get { return objc_getAssociatedObject(self, &AssociatedKeys.orgConstraintConstant) as? CGFloat }
set { objc_setAssociatedObject(self, &AssociatedKeys.orgConstraintConstant, newValue, .OBJC_ASSOCIATION_COPY) }
}
var orgTabBarAlpha: CGFloat? {
get { return objc_getAssociatedObject(self, &AssociatedKeys.orgTabBarAlpha) as? CGFloat }
set { objc_setAssociatedObject(self, &AssociatedKeys.orgTabBarAlpha, newValue, .OBJC_ASSOCIATION_COPY) }
}
func setTabBarVisible(visible:Bool, animated:Bool, bottomConstraint: NSLayoutConstraint) {
// bail if the current state matches the desired state
if (tabBarIsVisible() == visible) { return }
//define segment animation duration (note we have two segments so total animation time = times 2x)
let segmentAnimationDuration = animated ? 0.15 : 0.0
//we should show it
if visible {
//animate moving up
UIView.animate(withDuration: segmentAnimationDuration,
delay: 0,
options: [],
animations: {
[weak self] in
guard let self = self else { return }
bottomConstraint.constant = self.orgConstraintConstant ?? 0
self.view.layoutIfNeeded()
},
completion: {
(_) in
//animate tabbar fade in
UIView.animate(withDuration: segmentAnimationDuration) {
[weak self] in
guard let self = self else { return }
self.tabBar.alpha = self.orgTabBarAlpha ?? 0
self.view.layoutIfNeeded()
}
})
//reset our values
self.orgConstraintConstant = nil
}
//we should hide it
else {
//save our previous values
self.orgConstraintConstant = bottomConstraint.constant
self.orgTabBarAlpha = tabBar.alpha
//animate fade bar out
UIView.animate(withDuration: segmentAnimationDuration,
delay: 0,
options: [],
animations: {
[weak self] in
guard let self = self else { return }
self.tabBar.alpha = 0.0
self.view.layoutIfNeeded()
},
completion: {
(_) in
//then animate moving down
UIView.animate(withDuration: segmentAnimationDuration) {
[weak self] in
guard let self = self else { return }
bottomConstraint.constant = bottomConstraint.constant - self.tabBar.frame.height + 4 // + 4 looks nicer on no-home button devices
//self.tabBar.alpha = 0.0
self.view.layoutIfNeeded()
}
})
}
}
func tabBarIsVisible() ->Bool {
return orgConstraintConstant == nil
}
}
This is how it looks in my app (you can compare to my 1ste answer, the animation is a bit different but looks great) :