How to hide tab bar with animation in iOS?

我是研究僧i 提交于 2019-11-27 10:39:18
lara

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");
    }];
}

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:

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)

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.

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

Rewrite Sherwin Zadeh's answer in Swift 4:

/* tab bar hide/show animation */
extension AlbumViewController {
    // 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)? = nil ) {

        // bail if the current state matches the desired state
        if (tabBarIsVisible() == visible) {
            if let completion = completion {
                return completion(true)
            }
            else {
                return
            }
        }

        // 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 ? kFullScreenAnimationTime : 0.0)

        UIView.animate(withDuration: duration, animations: {
            let frame = self.tabBarController!.tabBar.frame
            self.tabBarController!.tabBar.frame = frame.offsetBy(dx: 0, dy: offsetY)
        }, completion:completion)
    }

    func tabBarIsVisible() -> Bool {
        return tabBarController!.tabBar.frame.origin.y < view.frame.maxY
    }
}

Try to set the frame of the tabBar in animation. See this tutorial.

Just be aware, it's bad practice to do that, you should set show/hide tabBar when UIViewController push by set the property hidesBottomBarWhenPushed to YES.

[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?()
        }
    }
}

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

Unfortunately, I can't comment on HixField's answer because I don't have enough reputation, so I have to leave this as a separate answer.

His answer is missing the computed property for movedFrameView, which is:

var movedFrameView:CGRect? {
  get { return objc_getAssociatedObject(self, &AssociatedKeys.movedFrameView) as? CGRect }
  set { objc_setAssociatedObject(self, &AssociatedKeys.movedFrameView, newValue, .OBJC_ASSOCIATION_COPY) }
}

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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!