0

I'm trying to get nested TabViews working in SwiftUI to achieve an onboarding flow prior to the main tabbed app screen but am running in to a non-obvious visual glitch.

I'd like the onboarding portion to be full-screen, ignoring safe areas, but the nested, tabbed, main app screen to honour safe areas.

The code below shows the glitch off: it's full-screen for the first couple of screens, but then - only on initial display - the tabs are below the safe area. If I swipe back and forwards again the tabs do then honour the safe area.

I've tried various combinations of .edgesIgnoringSafeArea(), .frame(), .offset(), as well as trying to make use of the values that GeometryReader provides. Removing the one .edgesIgnoringSafeArea() that is there gives me the (expected) bars below and above my tabs. Due to the swipe transitions I need the tabs to be full-screen.

I've also tried using .overlays to achieve the desired appearance and while this does work it requires more complex state manipulation and just feels wrong.

Finally, I've played around with nesting NavigationViews and TabViews and, as reported elsewhere, that rarely ends well.

I'd be grateful if someone could explain why I'm seeing this glitch (i.e. the gap in my understanding of SwiftUI's rendering/lifecycle, and why the nested tab bar changes position the second time it appears), and if there's a canonical way of achieving what I want. TIA.

// A simple tab view that can self-advance to the next tab.
// Included to simplify the main ContentView
struct SimpleTab: View {

    @Binding var tab: Int
    let label: String
    let backgroundColour: Color
    let withNext: Bool

    var body: some View {
        VStack {
            Spacer()
            Text(label)
                .frame(maxWidth: .infinity, alignment: .center)
            if withNext {
                Button {
                    withAnimation {
                        tab += 1
                    }
                } label: {
                    Text("Next ->")
                }
            }
            Spacer()
        }
        .background(backgroundColour
            .opacity(0.5))
    }
}

struct ContentView: View {

    @State var tab = 0
    @State var nestedTab = 0

    var body: some View {
        VStack {
            TabView(selection: $tab) {

                SimpleTab(tab: $tab, label: "Onboarding 1", backgroundColour: .red, withNext: true)
                    .tag(0)

                SimpleTab(tab: $tab, label: "Onboarding 2", backgroundColour: .green, withNext: true)
                    .tag(1)

                TabView(selection: $nestedTab) {
                    SimpleTab(tab: $nestedTab, label: "Nested 0", backgroundColour: .blue, withNext: false)
                        .tabItem {
                            Label("Nested 0", systemImage: "0.circle")
                        }
                        .tag(0)
                    SimpleTab(tab: $nestedTab, label: "Nested 1", backgroundColour: .blue, withNext: false)
                        .tabItem {
                            Label("Nested 1", systemImage: "1.circle")
                        }
                        .tag(1)
                }
                .tabViewStyle(DefaultTabViewStyle())
                .tag(2)
            }
            .edgesIgnoringSafeArea(.all)
            .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
            .transition(.slide)
        }
    }
}
Robin Macharg
  • 1,468
  • 14
  • 22

0 Answers0