Search code examples
iosswiftuiswiftui-tabview

SwiftUI TabView Updating Selection Does Not Update Selected Tab


For some reason updating the selection does not update the selected tab in this very simple example. My expectation would be that it would show Tab 1 initially, and when I press the Toggle Tab button, it should slide over to Tab 0. Instead I have to toggle the tab 3 times before it finally slides to Tab 0.

Tested in iOS 16

struct Test: View {
    @State var currentTab = 1
    var body: some View {
        VStack {
            Text("Current Tab: \(currentTab)")
            TabView(selection: $currentTab) {
                Text("Tab 0")
                    .tag(0)
                Text("Tab 1")
                    .tag(1)
            }
            .tabViewStyle(.page(indexDisplayMode: .never))
            Button {
                withAnimation(Animation.easeIn(duration: 0.2)) {
                    if currentTab == 0 {
                        currentTab = 1
                    } else {
                        currentTab = 0
                    }
                }
            } label: {
                Text("Toggle Tab")
            }
        }
    }
}

Example of the issue in an iOS 16.1 simulator:


Solution

  • Oh boy. There seem to be bugs specifically around when TabView's initial selection is the second available tag and your first programmatic change to the selection value is to go down to the first tag. I say this because I've tried this with an Int range of 0 to 30 and can reproduce specifically only when attempting to 1) start with an initial selection of 1 and then 2) decrementing to 0. Incrementing to 3 works. Starting at 0 and incrementing works. I feel like I'm missing something weird, but it's also possible that this is simply a bug. I would not be shocked, given that the behavior of TabView is so different when shown as PageTabView, and you don't see a ton of usage of PageTabView-like UI elements that don't start on their first page.

    Anyways. As dirty as it is, the only reliable fix I currently have is to simply programmatically toggle down and up in .onAppear. I can think of other ideas for possible workarounds, but for now I felt that posting this is better than leaving you without any feedback.

    Note that the placement of .onAppear on a Group around the children is crucial. Place it on the initially-displayed child and it will interfere with later usage because .onAppear is called as the user returns to the initially-displayed child. Place it on the TabView and it seems to be called too early to have the desired hacky impact.

    struct Test: View {
        @State var currentTab = 1
        var body: some View {
            VStack {
                Text("Current Tab: \(currentTab)")
                TabView(selection: $currentTab) {
                    Group {
                        Text("Tab 0")
                            .tag(0)
                        Text("Tab 1")
                            .tag(1)
                    }
                    .onAppear {
                        currentTab = 0
                        currentTab = 1
                    }
                }
    
                .tabViewStyle(.page(indexDisplayMode: .never))
    
                Button {
                    withAnimation(Animation.easeIn(duration: 0.2)) {
                        if currentTab == 0 {
                            currentTab = 1
                        } else {
                            currentTab = 0
                        }
                    }
                } label: {
                    Text("Toggle Tab")
                }
            }
        }
    }