iOS
Swift
Tab Bar
App Development
User Interface

Hide tab bar in IOS swift app

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Hiding the tab bar in an iOS app is straightforward for simple push flows, but bugs appear quickly in real navigation stacks. Common issues include clipped bottom content, flicker during interactive gestures, and tab bar state desynchronization across controllers. A reliable implementation depends on whether tab visibility is static per screen or dynamic based on user interaction.

Use the Native Static Approach First

If a destination screen should always hide the tab bar, use hidesBottomBarWhenPushed. This is the most stable option because UIKit handles restore behavior automatically.

swift
1import UIKit
2
3final class HomeViewController: UIViewController {
4    override func viewDidLoad() {
5        super.viewDidLoad()
6        view.backgroundColor = .systemBackground
7        title = "Home"
8    }
9
10    func openDetails() {
11        let vc = DetailsViewController()
12        vc.hidesBottomBarWhenPushed = true
13        navigationController?.pushViewController(vc, animated: true)
14    }
15}
16
17final class DetailsViewController: UIViewController {
18    override func viewDidLoad() {
19        super.viewDidLoad()
20        view.backgroundColor = .secondarySystemBackground
21        title = "Details"
22    }
23}

For many apps, this is all you need. It is easier to maintain than frame animations.

Dynamic Hide and Show With Animation

If the tab bar must appear or disappear based on scrolling or mode changes, create one shared method to control visibility. Keep this in one place so multiple controllers do not conflict.

swift
1import UIKit
2
3extension UIViewController {
4    func setTabBar(hidden: Bool, animated: Bool) {
5        guard let tabBar = tabBarController?.tabBar else { return }
6        guard tabBar.isHidden != hidden else { return }
7
8        let height = tabBar.frame.height
9        let offsetY = hidden ? height : -height
10
11        let updates = {
12            tabBar.frame = tabBar.frame.offsetBy(dx: 0, dy: offsetY)
13            let insetDelta = hidden ? -height : height
14            self.additionalSafeAreaInsets.bottom += insetDelta
15            self.view.layoutIfNeeded()
16        }
17
18        if animated {
19            UIView.animate(withDuration: 0.25, animations: updates) { _ in
20                tabBar.isHidden = hidden
21            }
22        } else {
23            updates()
24            tabBar.isHidden = hidden
25        }
26    }
27}

This method updates both frame and safe-area insets. Without inset updates, bottom controls can become inaccessible.

Lifecycle Placement Matters

For dynamic behavior tied to screen entry and exit, use lifecycle methods carefully.

swift
1override func viewWillAppear(_ animated: Bool) {
2    super.viewWillAppear(animated)
3    setTabBar(hidden: true, animated: animated)
4}
5
6override func viewWillDisappear(_ animated: Bool) {
7    super.viewWillDisappear(animated)
8    setTabBar(hidden: false, animated: animated)
9}

Keep ownership single. If parent and child controllers both toggle tab visibility, race conditions can cause flicker.

In complex flows, a coordinator object can centralize this policy and expose one method such as setTabVisibility(for screenType).

Scroll-Driven Behavior Pattern

A common requirement is hide on downward scroll and show on upward scroll. Tie state changes to threshold-based scroll deltas to avoid rapid toggling.

swift
1import UIKit
2
3final class FeedViewController: UITableViewController {
4    private var lastOffsetY: CGFloat = 0
5
6    override func scrollViewDidScroll(_ scrollView: UIScrollView) {
7        let currentY = scrollView.contentOffset.y
8        let delta = currentY - lastOffsetY
9
10        if delta > 12 {
11            setTabBar(hidden: true, animated: true)
12        } else if delta < -12 {
13            setTabBar(hidden: false, animated: true)
14        }
15
16        lastOffsetY = currentY
17    }
18}

Thresholds prevent jitter on minor scroll changes.

SwiftUI Interop Note

In mixed UIKit and SwiftUI apps, tab bar control can become inconsistent if both layers issue visibility changes. Choose one owner, usually UIKit coordinator or root container, and route all tab-visibility requests through that owner. This keeps transitions coherent during gesture navigation and deep links.

Testing Checklist

Before release, test tab bar behavior across:

  • push and pop navigation
  • interactive swipe-back cancellation
  • keyboard show and hide
  • rotation changes
  • deep links directly to hidden-tab screens

Add at least one UI test for hide on push and show on pop. This catches regressions when navigation code is refactored.

Common Pitfalls

A common pitfall is toggling tabBar.isHidden directly without adjusting safe-area insets, which causes clipped bottom controls. Another is applying dynamic logic where static hidesBottomBarWhenPushed would be simpler. Teams also let multiple controllers manipulate tab state independently, creating non-deterministic flicker. Gesture cancellations are frequently untested and expose half-applied states. Finally, keyboard interactions often reveal layout bugs that were not visible during basic navigation tests. Visual correctness and layout correctness need to be treated as the same problem here.

Summary

  • Prefer hidesBottomBarWhenPushed for static per-screen tab hiding.
  • For dynamic behavior, animate frame and safe-area updates together.
  • Keep one owner for tab visibility state to avoid conflicts.
  • Use thresholds for scroll-driven hide and show logic.
  • Test interactive gestures, keyboard, rotation, and deep-link flows.
  • Add UI tests for core visibility transitions to prevent regressions.

Course illustration
Course illustration

All Rights Reserved.