← All shorts
17iOS 18+2 min read

Tab and .sidebarAdaptable: One Tab Bar for iPhone and iPad

The new Tab builder plus .tabViewStyle(.sidebarAdaptable) turns one TabView into an iPad sidebar and an iPhone tab bar automatically.

Supporting a sidebar on iPad and a tab bar on iPhone from one navigation model used to mean maintaining two separate pieces of UI — a TabView for compact width and a NavigationSplitView with its own selection state for regular width — kept in sync by hand.

#The old way

if horizontalSizeClass == .compact {
    TabView(selection: $selection) {
        HomeView().tag(AppTab.home)
        SearchView().tag(AppTab.search)
    }
} else {
    NavigationSplitView {
        List(selection: $selection) {
            Label("Home", systemImage: "house").tag(AppTab.home)
            Label("Search", systemImage: "magnifyingglass").tag(AppTab.search)
        }
    } detail: {
        selection.destinationView
    }
}

#The new way

enum AppTab: Hashable {
    case home, search, library, profile
}

struct RootView: View {
    @State private var selection: AppTab = .home

    var body: some View {
        TabView(selection: $selection) {
            Tab("Home", systemImage: "house", value: AppTab.home) {
                HomeView()
            }
            Tab("Search", systemImage: "magnifyingglass", value: AppTab.search) {
                SearchView()
            }
            Tab("Library", systemImage: "books.vertical", value: AppTab.library) {
                LibraryView()
            }
            Tab("Profile", systemImage: "person.crop.circle", value: AppTab.profile) {
                ProfileView()
            }
        }
        .tabViewStyle(.sidebarAdaptable)
    }
}

#Why it matters

  • One TabView declaration drives both layouts — no duplicated selection state to keep in sync
  • iPad gets a real sidebar with a built-in collapse toggle; iPhone keeps the standard bottom tab bar
  • Value-based Tab items compose with TabSection for grouping tabs under headings when the sidebar is showing

#Gotcha

.tabViewStyle(.sidebarAdaptable) only works when every tab is declared with the new Tab builder syntax — you can't mix in the older .tabItem() modifier inside the same TabView, it either fails to compile or silently drops the sidebar behavior. If you add Tab(role: .search) for a dedicated search field, it has to live alongside the other Tab values in the same builder too; mixing Tab(role:) with .tabItem() is a documented compile error.

swiftuiios18navigation

Related shorts