29

I am trying to mimic the UINavigationController's new hidesBarsOnTap with a tab bar. I have seen many answers to this that either point to setting the hidesBottomBarWhenPushed on a viewController which only hides it entirely and not when tapped.

 @IBAction func tapped(sender: AnyObject) {

    // what goes here to show/hide the tabBar ???


}

thanks in advance

EDIT: as per the suggestion below I tried

self.tabBarController?.tabBar.hidden = true

which does indeed hide the tabBar (toggles true/false on tap), but without animation. I will ask that as a separate question though.

Michael Campsall
  • 4,325
  • 11
  • 37
  • 52

10 Answers10

57

After much hunting and trying out various methods to gracefully hide/show the UITabBar using Swift I was able to take this great solution by danh and convert it to Swift:

func setTabBarVisible(visible: Bool, animated: Bool) {

    //* This cannot be called before viewDidLayoutSubviews(), because the frame is not set before this time

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

    // get a frame calculation ready
    let frame = self.tabBarController?.tabBar.frame
    let height = frame?.size.height
    let offsetY = (visible ? -height! : height)

    // zero duration means no animation
    let duration: TimeInterval = (animated ? 0.3 : 0.0)

    //  animate the tabBar
    if frame != nil {
        UIView.animate(withDuration: duration) {
            self.tabBarController?.tabBar.frame = frame!.offsetBy(dx: 0, dy: offsetY!)
            return
        }
    }
}

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

// Call the function from tap gesture recognizer added to your view (or button)

@IBAction func tapped(_ sender: Any?) {
    setTabBarVisible(visible: !tabBarIsVisible(), animated: true)
}
Karen Hovhannisyan
  • 1,140
  • 2
  • 21
  • 31
Michael Campsall
  • 4,325
  • 11
  • 37
  • 52
  • 4
    this works great, but `tabBarIsVisible()` could be improved to handle cases like `UIViewController` embedded in `UINavigationController` like this: `func tabBarIsVisible() -> Bool { return self.tabBarController?.tabBar.frame.origin.y < UIScreen.mainScreen().bounds.height }` I refactored [this as class extension](https://gist.github.com/2bd8139c5f69b9434008.git) – Krodak May 12 '15 at 12:08
  • 1
    url is depreciated :( – fatihyildizhan Aug 21 '15 at 16:33
  • Bad approach, changing offset of UIView is never a good idea, causes issues when in-call status bar appears. – Codetard Nov 18 '17 at 14:17
  • please edit this answer for swift 4 because it doesn't work in swift 4 – Saeed Rahmatolahi Nov 21 '17 at 08:07
  • I no longer code in Swift and won't be updating this answer to Swift 4 (maybe it remains useful to those not yet using Swift 4).Someone could add another answer with Swift 4 code as Kie and Allocate have done with their answers for 2.3 and 3. – Michael Campsall Nov 21 '17 at 22:21
26

Loved Michael Campsall's answer. Here's the same code as extension, if somebody is interested:

Swift 2.3

extension UITabBarController {

    func setTabBarVisible(visible:Bool, animated:Bool) {

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

        // get a frame calculation ready
        let frame = self.tabBar.frame
        let height = frame.size.height
        let offsetY = (visible ? -height : height)

        // animate the tabBar
        UIView.animateWithDuration(animated ? 0.3 : 0.0) {
            self.tabBar.frame = CGRectOffset(frame, 0, offsetY)
            self.view.frame = CGRectMake(0, 0, self.view.frame.width, self.view.frame.height + offsetY)
            self.view.setNeedsDisplay()
            self.view.layoutIfNeeded()
        }
    }

    func tabBarIsVisible() ->Bool {
        return self.tabBar.frame.origin.y < CGRectGetMaxY(self.view.frame)
    }
}

Swift 3

extension UIViewController {

    func setTabBarVisible(visible: Bool, animated: Bool) {
        //* This cannot be called before viewDidLayoutSubviews(), because the frame is not set before this time

        // bail if the current state matches the desired state
        if (isTabBarVisible == visible) { return }

        // get a frame calculation ready
        let frame = self.tabBarController?.tabBar.frame
        let height = frame?.size.height
        let offsetY = (visible ? -height! : height)

        // zero duration means no animation
        let duration: TimeInterval = (animated ? 0.3 : 0.0)

        //  animate the tabBar
        if frame != nil {
            UIView.animate(withDuration: duration) {
                self.tabBarController?.tabBar.frame = frame!.offsetBy(dx: 0, dy: offsetY!)
                return
            }
        }
    }

    var isTabBarVisible: Bool {
        return (self.tabBarController?.tabBar.frame.origin.y ?? 0) < self.view.frame.maxY
    }
}
Kof
  • 23,893
  • 9
  • 56
  • 81
kiecodes
  • 1,642
  • 14
  • 28
  • where do i call this? – Just a coder Apr 15 '17 at 21:22
  • In your ViewController with the TabBar. In the method you want to change the visibility of your tabbar in. – kiecodes Apr 15 '17 at 23:11
  • isTabBarVisible function does not work as expected on iPad. Refer to @Michael Campsall answer to fix this. – Bogy May 20 '17 at 08:46
  • Thanks for letting me know Bogy. It seems the added Swift 3 version from @Kof differs from the Swift 2.3 at some points. But Michael Campsall added a swift version already, so I think we're good here. Just take Micheal's. :) – kiecodes May 20 '17 at 10:11
13

I had to adapt the accepted answer to this question a bit. It was hiding the bar but my view wasn't sizing itself appropriately so I was left with a space at the bottom.

The following code successfully animates the hiding of the tab bar while resizing the view to avoid that issue.

Updated for Swift 3 (now with less ugly code)

func setTabBarVisible(visible: Bool, animated: Bool) {
    guard let frame = self.tabBarController?.tabBar.frame else { return }
    let height = frame.size.height
    let offsetY = (visible ? -height : height)
    let duration: TimeInterval = (animated ? 0.3 : 0.0)

    UIView.animate(withDuration: duration,
                   delay: 0.0,
                   options: UIViewAnimationOptions.curveEaseIn,
                   animations: { [weak self] () -> Void in
                    guard let weakSelf = self else { return }
                    weakSelf.tabBarController?.tabBar.frame = frame.offsetBy(dx: 0, dy: offsetY)
                    weakSelf.view.frame = CGRect(x: 0, y: 0, width: weakSelf.view.frame.width, height: weakSelf.view.frame.height + offsetY)
                    weakSelf.view.setNeedsDisplay()
                    weakSelf.view.layoutIfNeeded()
    })
}

func handleTap(recognizer: UITapGestureRecognizer) {
    setTabBarVisible(visible: !tabBarIsVisible(), animated: true)
}

func tabBarIsVisible() -> Bool {
    guard let tabBar = tabBarController?.tabBar else { return false }
    return tabBar.frame.origin.y < UIScreen.main.bounds.height
}

Older Swift 2 Version

func setTabBarVisible(visible: Bool, animated: Bool) {
    // hide tab bar
    let frame = self.tabBarController?.tabBar.frame
    let height = frame?.size.height
    var offsetY = (visible ? -height! : height)
    println ("offsetY = \(offsetY)")

    // zero duration means no animation
    let duration:NSTimeInterval = (animated ? 0.3 : 0.0)

    // animate tabBar
    if frame != nil {
        UIView.animateWithDuration(duration) {
            self.tabBarController?.tabBar.frame = CGRectOffset(frame!, 0, offsetY!)
            self.view.frame = CGRectMake(0, 0, self.view.frame.width, self.view.frame.height + offsetY!)
            self.view.setNeedsDisplay()
            self.view.layoutIfNeeded()
            return
        }
    }
}

@IBAction func handleTap(recognizer: UITapGestureRecognizer) {
    setTabBarVisible(!tabBarIsVisible(), animated: true)
}

func tabBarIsVisible() -> Bool {
    return self.tabBarController?.tabBar.frame.origin.y < UIScreen.mainScreen().bounds.height
}
allocate
  • 1,323
  • 3
  • 14
  • 28
  • Upvoted this as the code in the animation closure is needed to do this properly. If you slide the tabBar down you need to slide the view above it down as well or you will have a gap where the tabBar was previously. Don't forget to update the tabBarIsVisible code as well as indicated – Wizkid Nov 15 '15 at 18:30
  • It doesn't draw the self.view to it full size like intended because it should set self.tabBarController?.tabBar.hidden = true and it will give you the actual full screen size but now you won't be able to animate the tab bar. – OhadM Mar 16 '16 at 09:37
  • Excellent! Thank you! I've called the function in `touchesBegan` so I could touch the screen to toggle ;) – Sylar Jan 16 '17 at 17:07
  • The white space is still there – Sorin Lica Aug 02 '19 at 11:30
8

You can just add this line to ViewDidLoad() in swift :

self.tabBarController?.tabBar.hidden = true
BSK-Team
  • 1,750
  • 1
  • 19
  • 37
2

I use tabBar.hidden = YES in ObjC to hide the tab bar in certain cases. I have not tried wiring it up to a tap event, though.

Mike Taverne
  • 9,156
  • 2
  • 42
  • 58
2

Code is okay but when you use presentViewController, tabBarIsVisible() is not working. To keep UITabBarController always hidden use just this part:

extension UITabBarController {
    func setTabBarVisible(visible:Bool, animated:Bool) {
        let frame = self.tabBar.frame
        let height = frame.size.height
        let offsetY = (visible ? -height : height)
        UIView.animateWithDuration(animated ? 0.3 : 0.0) {
            self.tabBar.frame = CGRectOffset(frame, 0, offsetY)
            self.view.frame = CGRectMake(0, 0, self.view.frame.width, self.view.frame.height + offsetY)
            self.view.setNeedsDisplay()
            self.view.layoutIfNeeded()
        }
    }
}
fatihyildizhan
  • 8,614
  • 7
  • 64
  • 88
1

Swift 3 version:

func setTabBarVisible(visible:Bool, animated:Bool) {

    //* This cannot be called before viewDidLayoutSubviews(), because the frame is not set before this time

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

    // get a frame calculation ready
    let frame = self.tabBarController?.tabBar.frame
    let height = frame?.size.height
    let offsetY = (visible ? -height! : height)

    // zero duration means no animation
    let duration:TimeInterval = (animated ? 0.3 : 0.0)

    //  animate the tabBar
    if frame != nil {
        UIView.animate(withDuration: duration) {

            self.tabBarController?.tabBar.frame = (self.tabBarController?.tabBar.frame.offsetBy(dx: 0, dy: offsetY!))!
            return
        }
    }
}

func tabBarIsVisible() ->Bool {
    return (self.tabBarController?.tabBar.frame.origin.y)! < self.view.frame.midY
}
Okan
  • 410
  • 4
  • 10
1

Swift 5

To hide

  override func viewWillAppear(_ animated: Bool) {
     self.tabBarController?.tabBar.isHidden = true
   }

To show again

   override func viewDidDisappear(_ animated: Bool) {
    self.tabBarController?.tabBar.isHidden = false
  }
Atmaram
  • 494
  • 4
  • 14
0

For Swift 4, and animating + hiding by placing tabBar outside the view:

if let tabBar = tabBarController?.tabBar,
   let y = tabBar.frame.origin.y + tabBar.frame.height {
   UIView.animate(withDuration: 0.2) {
     tabBar.frame = CGRect(origin: CGPoint(x: tabBar.frame.origin.x, y: y), size: tabBar.frame.size)
   }
}
traneHead
  • 986
  • 10
  • 15
-3

To make the animations work with self.tabBarController?.tabBar.hidden = true just do this:

UIView.animateWithDuration(0.2, animations: {
    self.tabBarController?.tabBar.hidden = true
})

Other than the other solution this will also work nicely with autolayout.

krizzzn
  • 1,413
  • 11
  • 13
  • Have you tried this piece of code...? It doesn't work for me. – Fengson Oct 01 '15 at 14:13
  • It's pasted directly from my project into Stackoverflow. Did you put it into your `@IBAction func tapped(sender: AnyObject)` function? Did you attach a debugger yet and see if your app stops inside the `animateWithDuration` block? – krizzzn Oct 01 '15 at 15:38
  • The code-block is meant to be added to a controller that is part of a tabBarController. – krizzzn Oct 01 '15 at 15:41
  • I'm not stupid, I know how to do it man :) The code works in a matter that it hides the tab bar, but there is no animation for me. Which is understandable, because you want to animate changing a bool value. That's why it won't work. – Fengson Oct 01 '15 at 19:34
  • 1
    animateWithDuration will have no effect since there is no change in the position of the tab bar frame. The only effect is that the tab tar will be gone. – OhadM Mar 16 '16 at 07:47