TabBar
programmatic navigation
SwiftUI
iOS development
app design

Switching to a TabBar tab view programmatically?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Switching to a specific tab programmatically is done differently depending on the framework. In UIKit, set selectedIndex or selectedViewController on UITabBarController. In SwiftUI, bind a @State variable to TabView(selection:) and change that variable. In Android with Material Tabs, call TabLayout.getTabAt(index).select(). This is needed when deep links, push notifications, or user actions in one tab should navigate to another tab.

UIKit (Swift)

Using selectedIndex

swift
1class MyViewController: UIViewController {
2
3    func switchToSecondTab() {
4        // selectedIndex is 0-based
5        self.tabBarController?.selectedIndex = 1
6    }
7}

Using selectedViewController

swift
1func switchToProfileTab() {
2    guard let tabBarController = self.tabBarController else { return }
3
4    // Find the navigation controller containing ProfileViewController
5    for (index, vc) in tabBarController.viewControllers!.enumerated() {
6        if let nav = vc as? UINavigationController,
7           nav.viewControllers.first is ProfileViewController {
8            tabBarController.selectedIndex = index
9            break
10        }
11    }
12}
swift
1func application(_ app: UIApplication, open url: URL,
2                 options: [UIApplication.OpenURLOptionsKey: Any]) -> Bool {
3
4    guard let tabBarController = window?.rootViewController as? UITabBarController else {
5        return false
6    }
7
8    if url.host == "profile" {
9        tabBarController.selectedIndex = 2
10    } else if url.host == "settings" {
11        tabBarController.selectedIndex = 3
12    }
13
14    return true
15}

Handling UITabBarControllerDelegate

Control whether a tab switch should be allowed:

swift
1class MainTabBarController: UITabBarController, UITabBarControllerDelegate {
2
3    override func viewDidLoad() {
4        super.viewDidLoad()
5        self.delegate = self
6    }
7
8    func tabBarController(_ tabBarController: UITabBarController,
9                          shouldSelect viewController: UIViewController) -> Bool {
10        // Prevent switching to a tab that requires authentication
11        if viewController is ProfileViewController && !UserSession.isLoggedIn {
12            presentLoginScreen()
13            return false
14        }
15        return true
16    }
17}

SwiftUI

TabView with Selection Binding

swift
1struct ContentView: View {
2    @State private var selectedTab = 0
3
4    var body: some View {
5        TabView(selection: $selectedTab) {
6            HomeView()
7                .tabItem {
8                    Label("Home", systemImage: "house")
9                }
10                .tag(0)
11
12            SearchView()
13                .tabItem {
14                    Label("Search", systemImage: "magnifyingglass")
15                }
16                .tag(1)
17
18            ProfileView(switchToHome: { selectedTab = 0 })
19                .tabItem {
20                    Label("Profile", systemImage: "person")
21                }
22                .tag(2)
23        }
24    }
25}

Switching from a Child View

Pass the binding or use a closure:

swift
1struct ProfileView: View {
2    var switchToHome: () -> Void
3
4    var body: some View {
5        VStack {
6            Text("Profile")
7            Button("Go to Home") {
8                switchToHome()
9            }
10        }
11    }
12}

Using String Tags

swift
1struct ContentView: View {
2    @State private var selectedTab = "home"
3
4    var body: some View {
5        TabView(selection: $selectedTab) {
6            HomeView()
7                .tabItem { Label("Home", systemImage: "house") }
8                .tag("home")
9
10            SettingsView()
11                .tabItem { Label("Settings", systemImage: "gear") }
12                .tag("settings")
13        }
14        .onOpenURL { url in
15            if url.host == "settings" {
16                selectedTab = "settings"
17            }
18        }
19    }
20}

Android (Kotlin)

TabLayout with ViewPager2

kotlin
1class MainActivity : AppCompatActivity() {
2
3    private lateinit var tabLayout: TabLayout
4    private lateinit var viewPager: ViewPager2
5
6    override fun onCreate(savedInstanceState: Bundle?) {
7        super.onCreate(savedInstanceState)
8        setContentView(R.layout.activity_main)
9
10        tabLayout = findViewById(R.id.tabLayout)
11        viewPager = findViewById(R.id.viewPager)
12
13        // Switch to tab at index 2
14        switchToTab(2)
15    }
16
17    fun switchToTab(index: Int) {
18        tabLayout.getTabAt(index)?.select()
19        viewPager.currentItem = index
20    }
21}

Bottom Navigation (Jetpack)

kotlin
1class MainActivity : AppCompatActivity() {
2
3    private lateinit var bottomNav: BottomNavigationView
4
5    override fun onCreate(savedInstanceState: Bundle?) {
6        super.onCreate(savedInstanceState)
7        setContentView(R.layout.activity_main)
8
9        bottomNav = findViewById(R.id.bottom_navigation)
10
11        // Switch programmatically
12        bottomNav.selectedItemId = R.id.nav_profile
13    }
14}

Jetpack Compose

kotlin
1@Composable
2fun MainScreen() {
3    var selectedTab by remember { mutableIntStateOf(0) }
4    val tabs = listOf("Home", "Search", "Profile")
5
6    Scaffold(
7        bottomBar = {
8            NavigationBar {
9                tabs.forEachIndexed { index, title ->
10                    NavigationBarItem(
11                        selected = selectedTab == index,
12                        onClick = { selectedTab = index },
13                        label = { Text(title) },
14                        icon = { /* icon */ }
15                    )
16                }
17            }
18        }
19    ) { padding ->
20        when (selectedTab) {
21            0 -> HomeScreen(onNavigateToProfile = { selectedTab = 2 })
22            1 -> SearchScreen()
23            2 -> ProfileScreen()
24        }
25    }
26}

React Native

jsx
1import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
2import { useNavigation } from '@react-navigation/native';
3
4const Tab = createBottomTabNavigator();
5
6function HomeScreen() {
7  const navigation = useNavigation();
8
9  return (
10    <Button
11      title="Go to Profile"
12      onPress={() => navigation.navigate('Profile')}
13    />
14  );
15}
16
17function App() {
18  return (
19    <Tab.Navigator>
20      <Tab.Screen name="Home" component={HomeScreen} />
21      <Tab.Screen name="Search" component={SearchScreen} />
22      <Tab.Screen name="Profile" component={ProfileScreen} />
23    </Tab.Navigator>
24  );
25}

Common Pitfalls

  • Setting selectedIndex before tabs are loaded: In UIKit, setting selectedIndex in viewDidLoad of the tab bar controller before all child view controllers are added has no effect. Set it in viewDidAppear or after adding all tabs.
  • Forgetting .tag() in SwiftUI TabView: Without .tag() on each tab, the selection binding cannot match tabs. Tags must be the same type as the @State variable (e.g., all Int or all String).
  • Not syncing TabLayout with ViewPager2 on Android: Calling tabLayout.getTabAt(index)?.select() alone does not scroll the ViewPager. Also set viewPager.currentItem = index to keep both in sync.
  • Animating tab switches when not desired: UIKit animates the tab switch by default with selectedViewController. To switch without animation, use selectedIndex directly. In SwiftUI, wrap the assignment in withAnimation(nil) to suppress animation.
  • Switching tabs from a background thread: UI updates must happen on the main thread. In UIKit, wrap in DispatchQueue.main.async { }. In SwiftUI, ensure @State changes happen on the @MainActor.

Summary

  • In UIKit, set tabBarController.selectedIndex or selectedViewController to switch tabs
  • In SwiftUI, bind a @State variable to TabView(selection:) and update the variable
  • In Android, use tabLayout.getTabAt(index)?.select() with viewPager.currentItem = index
  • In React Native, call navigation.navigate('TabName') from any screen
  • Always ensure tab tags/indices are set correctly and UI updates happen on the main thread

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.