How to hide tab bar with animation in iOS?

后端 未结 15 1804
夕颜
夕颜 2020-11-30 19:20

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

相关标签:
15条回答
  • 2020-11-30 20:01

    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)

    0 讨论(0)
  • 2020-11-30 20:02

    You can have a bug when animating manually the tab bar on iOS13 and Xcode 11. If the user press the home button after the animation (it'll just ignore the animation and will be there in the right place). I think it's a good idea to invert the animation before that by listening to the applicationWillResignActive event.

    0 讨论(0)
  • 2020-11-30 20:03

    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");
        }];
    }
    
    0 讨论(0)
  • 2020-11-30 20:07

    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:
    enter image description here

    0 讨论(0)
  • 2020-11-30 20:08

    For Xcode 11.3 and iOS 13 other answers didn't work for me. However, based on those I've came up to the new solution using CGAffineTransform

    I didn't test this code well, but this might actually work.

    extension UITabBarController {
    
        func setTabBarHidden(_ isHidden: Bool) {
    
            if !isHidden { tabBar.isHidden = false }
    
            let height = tabBar.frame.size.height
            let offsetY = view.frame.height - (isHidden ? 0 : height)
            tabBar.transform = CGAffineTransform(translationX: 0, y: offsetY)
    
            UIView.animate(withDuration: 0.25, animations: {
                self.tabBar.transform = .identity
            }) { _ in
                self.tabBar.isHidden = isHidden
            }
        }
    
    }
    

    Hope that helps.

    UPDATE 09.03.2020:

    I've finally found an awesome implementation of hiding tab bar with animation. It's huge advantage it's able to work either in common cases and in custom navigation controller transitions. Since author's blog is quite unstable, I'll leave the code below. Original source: https://www.iamsim.me/hiding-the-uitabbar-of-a-uitabbarcontroller/

    Implementation:

    extension UITabBarController {
    
        /**
         Show or hide the tab bar.
    
         - Parameter hidden: `true` if the bar should be hidden.
         - Parameter animated: `true` if the action should be animated.
         - Parameter transitionCoordinator: An optional `UIViewControllerTransitionCoordinator` to perform the animation
            along side with. For example during a push on a `UINavigationController`.
         */
        func setTabBar(
            hidden: Bool,
            animated: Bool = true,
            along transitionCoordinator: UIViewControllerTransitionCoordinator? = nil
        ) {
            guard isTabBarHidden != hidden else { return }
    
            let offsetY = hidden ? tabBar.frame.height : -tabBar.frame.height
            let endFrame = tabBar.frame.offsetBy(dx: 0, dy: offsetY)
            let vc: UIViewController? = viewControllers?[selectedIndex]
            var newInsets: UIEdgeInsets? = vc?.additionalSafeAreaInsets
            let originalInsets = newInsets
            newInsets?.bottom -= offsetY
    
            /// Helper method for updating child view controller's safe area insets.
            func set(childViewController cvc: UIViewController?, additionalSafeArea: UIEdgeInsets) {
                cvc?.additionalSafeAreaInsets = additionalSafeArea
                cvc?.view.setNeedsLayout()
            }
    
            // Update safe area insets for the current view controller before the animation takes place when hiding the bar.
            if hidden, let insets = newInsets { set(childViewController: vc, additionalSafeArea: insets) }
    
            guard animated else {
                tabBar.frame = endFrame
                return
            }
    
            // Perform animation with coordinato if one is given. Update safe area insets _after_ the animation is complete,
            // if we're showing the tab bar.
            weak var tabBarRef = self.tabBar
            if let tc = transitionCoordinator {
                tc.animateAlongsideTransition(in: self.view, animation: { _ in tabBarRef?.frame = endFrame }) { context in
                    if !hidden, let insets = context.isCancelled ? originalInsets : newInsets {
                        set(childViewController: vc, additionalSafeArea: insets)
                    }
                }
            } else {
                UIView.animate(withDuration: 0.3, animations: { tabBarRef?.frame = endFrame }) { didFinish in
                    if !hidden, didFinish, let insets = newInsets {
                        set(childViewController: vc, additionalSafeArea: insets)
                    }
                }
            }
        }
    
        /// `true` if the tab bar is currently hidden.
        var isTabBarHidden: Bool {
            return !tabBar.frame.intersects(view.frame)
        }
    
    }
    

    If you're dealing with custom navigation transitions just pass a transitionCoordinator property of "from" controller, so animations are in sync:

    from.tabBarController?.setTabBar(hidden: true, along: from.transitionCoordinator)
    

    Note, that in such case the initial solution work very glitchy.

    0 讨论(0)
  • 2020-11-30 20:09

    As per Apple docs, hidesBottomBarWhenPushed property of UIViewController, a Boolean value, indicating whether the toolbar at the bottom of the screen is hidden when the view controller is pushed on to a navigation controller.

    The value of this property on the topmost view controller determines whether the toolbar is visible.

    The recommended approach to hide tab bar would as follows

        ViewController *viewController = [[ViewController alloc] init];
        viewController.hidesBottomBarWhenPushed = YES;  // This property needs to be set before pushing viewController to the navigationController's stack. 
        [self.navigationController pushViewController:viewController animated:YES];
    

    However, note this approach will only be applied to respective viewController and will not be propagated to other view controllers unless you start setting the same hidesBottomBarWhenPushed property in other viewControllers before pushing it to the navigation controller's stack.

    0 讨论(0)
提交回复
热议问题