How to hide tab bar with animation in iOS?

后端 未结 15 1806
夕颜
夕颜 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:10

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

    This wrks for me: [self.tabBar setHidden:YES];
    where self is the view controller, tabBar is the id for the tabBar.

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

    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

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

    [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?()
            }
        }
    }
    
    
    0 讨论(0)
  • 2020-11-30 20:21

    I went through the previous posts, so I came out with the solution below as subclass of UITabBarController

    Main points are:

    • Written in Swift 5.1
    • Xcode 11.3.1
    • Tested on iOS 13.3
    • Simulated on iPhone 11 and iPhone 8 (so with and without notch)
    • Handles the cases where the user taps on the different tabs
    • Handles the cases where we programmatically change the value of selectedIndex
    • Handles the view controller orientation changes
    • Handles the corner casere where the app moved to background and back to foreground

    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 :

    • Updated the code to respect the safe area bottom inset
    • If you're experiencing issues with this solution and your tab bar contains a navigation controller as direct child in the 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 problem

    Hope it helps someone :)

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

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

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