← 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.

Sidebar on iPad, tab bar on iPhone, one navigation model. That used to mean two separate pieces of UI: a TabView for compact width, a NavigationSplitView with its own selection state for regular width. You kept them 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 second copy of the 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, so you can group tabs under headings while the sidebar is showing

#Gotcha

.tabViewStyle(.sidebarAdaptable) needs every tab declared with the new Tab builder. Mix the older .tabItem() modifier into the same TabView and it either fails to compile or silently drops the sidebar behavior. Tab(role: .search) for a dedicated search field has to sit alongside the other Tab values in that same builder too. Mixing Tab(role:) with .tabItem() is a documented compile error.

swiftuiios18navigation

Related shorts