1

How can I animate slide transitions between different views?

In following sample code I made a Picker that chooses which view to show and my goal is to have a transition that looks similar to the NavigationLink transition. At the moment it doesn't animate at all. If I add the .animation(.easeInOut(duration: 2)) modifier to the ZStack it animates a fade animation for 2 seconds but I don't understand why.

struct ContentView: View {

    enum WhichScreen: String, CaseIterable {
        case red, blue, green, yellow
    }

    @State private var whichScreen = WhichScreen.red

    var body: some View {
        VStack {
            Picker("screen", selection: $whichScreen.animation()) {
                ForEach(WhichScreen.allCases, id: \.self) { value in
                    Text(value.rawValue).tag(value)
                }
            }
            .pickerStyle(SegmentedPickerStyle())
            ZStack {
                Color.black
                switch whichScreen {
                case .red:
                    Color.red
                case .blue:
                    Color.blue
                case .green:
                    Color.green
                case .yellow:
                    Color.yellow
                }
            }
            .transition(.slide)
        }
    }
}
Isaak
  • 1,107
  • 2
  • 11
  • 29

2 Answers2

3

Do you actually need the ZStack with the black Color (which is never visible in your example snippet)? Without it the transition works out of the box (in the simulator, not in the SwiftUI preview):

struct ContentView: View {

    enum WhichScreen: String, CaseIterable {
        case red, blue, green, yellow
    }

    @State private var whichScreen = WhichScreen.red

    var body: some View {
        VStack {
            Picker("screen", selection: $whichScreen.animation()) {
                ForEach(WhichScreen.allCases, id: \.self) { value in
                    Text(value.rawValue).tag(value)
                }
            }
            .pickerStyle(SegmentedPickerStyle())
            Group {
                switch whichScreen {
                case .red:
                    Color.red
                case .blue:
                    Color.blue
                case .green:
                    Color.green
                case .yellow:
                    Color.yellow
                }
            }
            .transition(.slide)
        }
    }

}
Ralf Ebert
  • 3,556
  • 3
  • 29
  • 43
  • Yes, that's right. In my particular use case I layer all of this on top of another view and in that case your code only works as desired when using the `.transition(.asymmetric(insertion: .move(edge: .trailing), removal: .move(edge: .leading)))` modifier. But then this is actually the simpler solution. Thank you. – Isaak Jul 17 '21 at 15:16
1

To get the slide animation you're looking for, the .transition needs to be applied to just the part you want sliding (eg, the non-black colors in this case):

ZStack {
    Color.black
    Group {
        switch whichScreen {
        case .red:
            Color.red
        case .blue:
            Color.blue
        case .green:
            Color.green
        case .yellow:
            Color.yellow
        }
    }.transition(.slide)
}

You'll still have one more step to take care of if you want it exactly like the NavigationLink transition, which is the fact that right now, the 'old' or 'previous' view disappears before the transition. If you want that included in the stack, you could do something like this:

struct ContentView: View {
    
    enum WhichScreen: String, CaseIterable {
        case red, blue, green, yellow
    }
    
    @State private var whichScreen = WhichScreen.red
    @State private var previousScreen : WhichScreen? = .none
    
    var body: some View {
        VStack {
            Picker("screen", selection: $whichScreen.animation()) {
                ForEach(WhichScreen.allCases, id: \.self) { value in
                    Text(value.rawValue).tag(value)
                }
            }
            .pickerStyle(SegmentedPickerStyle())
            .onChange(of: whichScreen) { [whichScreen] _ in
                previousScreen = whichScreen
            }
            ZStack {
                Color.black
                if let previousScreen = previousScreen {
                    switch(previousScreen) {
                    case .red:
                        Color.red
                    case .blue:
                        Color.blue
                    case .green:
                        Color.green
                    case .yellow:
                        Color.yellow
                    }
                    
                }
                Group {
                    switch whichScreen {
                    case .red:
                        Color.red
                    case .blue:
                        Color.blue
                    case .green:
                        Color.green
                    case .yellow:
                        Color.yellow
                    }
                }.transition(.slide)
            }
        }
    }
}
jnpdx
  • 45,847
  • 6
  • 64
  • 94
  • Great! Thank you very much! I just noticed that the transitions don't work in the preview. I don't know if I can trust preview ever again. – Isaak Jul 15 '21 at 23:56