How to hide tab bar with animation in iOS?
When working with storyboard its easy to setup the View Controller to hide the tabbar on push, on the destination View Controller just select this checkbox:
I try to keep view animations available to me using the following formula:
// pass a param to describe the state change, an animated flag and a completion block matching UIView animations completion - (void)setTabBarVisible:(BOOL)visible animated:(BOOL)animated completion:(void (^)(BOOL))completion { // bail if the current state matches the desired state if ([self tabBarIsVisible] == visible) return (completion)? completion(YES) : nil; // get a frame calculation ready CGRect frame = self.tabBarController.tabBar.frame; CGFloat height = frame.size.height; CGFloat offsetY = (visible)? -height : height; // zero duration means no animation CGFloat duration = (animated)? 0.3 : 0.0; [UIView animateWithDuration:duration animations:^{ self.tabBarController.tabBar.frame = CGRectOffset(frame, 0, offsetY); } completion:completion];}//Getter to know the current state- (BOOL)tabBarIsVisible { return self.tabBarController.tabBar.frame.origin.y < CGRectGetMaxY(self.view.frame);}//An illustration of a call to toggle current state- (IBAction)pressedButton:(id)sender { [self setTabBarVisible:![self tabBarIsVisible] animated:YES completion:^(BOOL finished) { NSLog(@"finished"); }];}
does not longer work on iOS14, see updated 2nde answer below
Swift 3.0 version, using an extension:
extension UITabBarController { private struct AssociatedKeys { // Declare a global var to produce a unique address as the assoc object handle static var orgFrameView: UInt8 = 0 static var movedFrameView: UInt8 = 1 } var orgFrameView:CGRect? { get { return objc_getAssociatedObject(self, &AssociatedKeys.orgFrameView) as? CGRect } set { objc_setAssociatedObject(self, &AssociatedKeys.orgFrameView, newValue, .OBJC_ASSOCIATION_COPY) } } var movedFrameView:CGRect? { get { return objc_getAssociatedObject(self, &AssociatedKeys.movedFrameView) as? CGRect } set { objc_setAssociatedObject(self, &AssociatedKeys.movedFrameView, newValue, .OBJC_ASSOCIATION_COPY) } } override open func viewWillLayoutSubviews() { super.viewWillLayoutSubviews() if let movedFrameView = movedFrameView { view.frame = movedFrameView } } func setTabBarVisible(visible:Bool, animated:Bool) { //since iOS11 we have to set the background colour to the bar color it seams the navbar seams to get smaller during animation; this visually hides the top empty space... view.backgroundColor = self.tabBar.barTintColor // bail if the current state matches the desired state if (tabBarIsVisible() == visible) { return } //we should show it if visible { tabBar.isHidden = false UIView.animate(withDuration: animated ? 0.3 : 0.0) { //restore form or frames self.view.frame = self.orgFrameView! //errase the stored locations so that... self.orgFrameView = nil self.movedFrameView = nil //...the layoutIfNeeded() does not move them again! self.view.layoutIfNeeded() } } //we should hide it else { //safe org positions orgFrameView = view.frame // get a frame calculation ready let offsetY = self.tabBar.frame.size.height movedFrameView = CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height + offsetY) //animate UIView.animate(withDuration: animated ? 0.3 : 0.0, animations: { self.view.frame = self.movedFrameView! self.view.layoutIfNeeded() }) { (_) in self.tabBar.isHidden = true } } } func tabBarIsVisible() ->Bool { return orgFrameView == nil }}
- This is based on the input from Sherwin Zadeh after a few hours of playing around.
- Instead of moving the tabbar itself it moves the frame of the view, this effectively slides the tabbar nicely out of the bottom of the screen but...
- ... has the advantage that the content displayed inside the UITabbarcontroller is then also taking the full screen!
- note its also using the AssociatedObject functionality to attached data to the UIView without subclassing and thus an extension is possible (extensions do not allow stored properties)