Fix: Tab Bar Missing After Applying .tabViewStyle(.page)

Fix: Tab Bar Missing After Applying .tabViewStyle(.page)

Written by

in

Quick answer: In SwiftUI, applying .tabViewStyle(.page) replaces the entire tab bar navigation UI with a horizontal page carousel. To keep standard tab bar navigation with paginated content, remove .tabViewStyle(.page) from the TabView and implement horizontal paging inside your child views using ScrollView(.horizontal) with .scrollTargetBehavior(.paging).

Why this happens

In SwiftUI, TabView serves two mutually exclusive interface roles depending on its style configuration. By default, it renders a standard bottom tab bar (UITabBarController on iOS). When you apply .tabViewStyle(.page), SwiftUI switches the underlying implementation to a horizontal paging container (UIPageViewController).

Because the page style treats the container strictly as a paginated carousel, it completely discards the standard tab bar UI. Instead of displaying icon tabs at the bottom of the screen, SwiftUI displays small page indicator dots (or nothing at all if you hide the index display).

If you want users to see a persistent bottom tab bar while also supporting swipeable, paginated views, you cannot apply .tabViewStyle(.page) directly to the root TabView.

How to fix it

1. Move Paging Inside Each Tab Using Paging Scroll Views

If you need full-screen pages inside a specific section while keeping the primary bottom tab bar intact, leave the root TabView in its default style. Inside the destination tab view, embed a horizontal ScrollView and attach .scrollTargetBehavior(.paging).

import SwiftUI

enum AppTab: Hashable {
    case deep
    case couple
    case gossip
}

struct ContentView: View {
    @State private var selectedTab: AppTab = .deep

    var body: some View {
        TabView(selection: $selectedTab) {
            Tab("Deep", systemImage: "play", value: .deep) {
                DeepPagingView()
            }

            Tab("Couple", systemImage: "heart", value: .couple) {
                Text("Couple Content")
            }

            Tab("Gossip", systemImage: "bubble.left", value: .gossip) {
                Text("Gossip Content")
            }
        }
    }
}

struct DeepPagingView: View {
    var body: some View {
        ScrollView(.horizontal) {
            LazyHStack(spacing: 0) {
                Color.blue
                    .containerRelativeFrame(.horizontal)
                Color.green
                    .containerRelativeFrame(.horizontal)
                Color.orange
                    .containerRelativeFrame(.horizontal)
            }
        }
        .scrollTargetBehavior(.paging)
        .scrollIndicators(.hidden)
    }
}

This keeps the system tab bar visible and interactive at the bottom while allowing horizontal swiping through pages inside DeepPagingView.

2. Pair a Paged TabView with a Custom Bottom Navigation Bar

If your goal is to let users swipe between the primary tabs themselves while maintaining custom bottom buttons, use a paged TabView paired with an overlay navigation bar bound to the same selection state.

import SwiftUI

enum AppTab: String, CaseIterable {
    case deep = "Deep"
    case couple = "Couple"
    case gossip = "Gossip"

    var icon: String {
        switch self {
        case .deep: return "play"
        case .couple: return "heart"
        case .gossip: return "bubble.left"
        }
    }
}

struct CustomPagedTabView: View {
    @State private var selectedTab: AppTab = .deep

    var body: some View {
        ZStack(alignment: .bottom) {
            TabView(selection: $selectedTab) {
                Text("Deep View Content")
                    .tag(AppTab.deep)

                Text("Couple View Content")
                    .tag(AppTab.couple)

                Text("Gossip View Content")
                    .tag(AppTab.gossip)
            }
            .tabViewStyle(.page(indexDisplayMode: .never))
            .ignoresSafeArea(edges: .bottom)

            customTabBar
        }
    }

    private var customTabBar: some View {
        HStack {
            ForEach(AppTab.allCases, id: \.self) { tab in
                Spacer()
                Button {
                    withAnimation {
                        selectedTab = tab
                    }
                } label: {
                    VStack(spacing: 4) {
                        Image(systemName: tab.icon)
                        Text(tab.rawValue)
                            .font(.caption)
                    }
                    .foregroundColor(selectedTab == tab ? .accentColor : .secondary)
                }
                Spacer()
            }
        }
        .padding(.vertical, 12)
        .background(.ultraThinMaterial)
    }
}

In this setup, .tabViewStyle(.page(indexDisplayMode: .never)) provides the horizontal swipe navigation between views, while the custom HStack replaces the missing system tab bar.

How to prevent it

  • Remember that a single TabView cannot simultaneously render as both a system UITabBar and a UIPageViewController.
  • Decide whether swiping is scoped to the entire screen navigation or limited to subviews inside an individual tab.
  • Avoid placing .tabViewStyle(.page) on top-level root containers if you rely on system Tab labels or system navigation bars.

FAQ

Can I make the system tab bar swipeable without custom views?

No. The native iOS tab bar controller pattern does not support horizontal swipe gestures between tabs. To achieve swiping between root tabs, you must either use .tabViewStyle(.page) with a custom tab bar overlay or manage horizontal scroll positions manually.

Why does my view freeze when combining a horizontal scroll view with a paged TabView?

Nesting a horizontal ScrollView inside a .page style TabView creates gesture collisions between the two horizontal scroll targets. Choose one container to handle the horizontal axis and keep the other vertical.

Does indexDisplayMode: .always bring back the tab bar buttons?

No. The indexDisplayMode parameter only configures the UIPageControl dot indicators at the bottom of the screen. It has no effect on tab titles or tab bar icons.

Sources & credits

Parts of this guide are adapted from content licensed under CC BY-SA 4.0; this article is shared under the same license.