Animate transition between views of TabView in SwiftUI

Hi, I have been trying for many hours to animate the transition between tabs on the TabView for my app in swiftUI. I have looked at many online resources but they are all outdated.

The main method I have tried is this:

@State private var selection: Tab = .home //tab variable from list of enums not shown

TabView(selection: $selection) {
    // content
}
.accentColor(//Color) //Works just fine
.animation(.easeInOut(duration: 0.3), value: selection) //Does nothing on preview or simulator
.transition(.slide) //Does nothing on preview or simulator

I really want the animation to work as it would make the app exactly how I would want it.

Here is how it looks:

Instead of having smooth transition between each tab, its a jarring instantaneous change. I would like to know how to slide from one screen to the other (without using PageView, as that removes the tabview that I want at the bottom).

Thanks.

Replies

When animations or transitions fail it is usually a problem of "identity". If SwiftUI can't figure out, which views belong to an animation because one is destroyed or created. Animation does not work.

See this WWDC Talk for an explanation: https://developer.apple.com/wwdc21/10022 

Check out the tab views used in Apples apps: Podcast, Music, Files... Switching between tabs is always instantaneous, by design.

Personally I don't think a transition animation between the tabs would add any benefit. It could even get annoying for a user.

If you really want to trigger some animation, you could use the onAppear of each tab to build some illusion... but this would mean that your views (within the TabView) would really need to know what view is being drawn left/right of it. I think you will end up writing your own version of TabView, not a good idea.

See also the HIG to learn why and when to use tab bars (https://developer.apple.com/design/human-interface-guidelines/tab-bars) in iOS.

But if you really need an animation... there is a possible way using the TabView in .page mode. The TabView will take care of the animation and will allow swiping between the screens, but you will have to add the bottom bar with all the buttons to switch between the pages. See the example below.

struct ContentView: View {
    @State private var selectedScreen = 0

    var body: some View {
        TabView(selection: $selectedScreen) {
            Text("Home")
                .tag(0)

            Text("Calendar")
                .tag(1)

            Text("Settings")
                .tag(2)
        }
        .tabViewStyle(.page(indexDisplayMode: .never))
        .toolbar {
            ToolbarItem(placement: .bottomBar) {
                HStack {
                    Button(action: { withAnimation { selectedScreen = 0 } }) {
                        VStack {
                            Image(systemName: "house")
                            Text("Home").font(.caption2)
                        }
                    }
                    .foregroundColor(selectedScreen == 0 ? .accentColor : .primary)
                    .frame(maxWidth: .infinity)

                    Button(action: { withAnimation { selectedScreen = 1 } }) {
                        VStack {
                            Image(systemName: "calendar")
                            Text("Calendar").font(.caption2)
                        }
                    }
                    .foregroundColor(selectedScreen == 1 ? .accentColor : .primary)
                    .frame(maxWidth: .infinity)

                    Button(action: { withAnimation { selectedScreen = 2 } }) {
                        VStack {
                            Image(systemName: "gear")
                            Text("Settings").font(.caption2)
                        }
                    }
                    .foregroundColor(selectedScreen == 2 ? .accentColor : .primary)
                    .frame(maxWidth: .infinity)
                }
                .buttonStyle(.plain)
                .labelStyle(.titleAndIcon)
            }
        }
    }
}