18

I'm working on a sequence of screens that should change on a button press and should slide in/out from right to left.
Everything is working nicely except the .slide transition makes it move in from the leading edge and move out to the trailing edge. (from left to right)
I want to reverse the transition animation direction without changing what trailing/leading is (at least not in the whole context of the app).

I tried changing the transition to .move(edge: .trailing) but then the views move in and out from the same direction.

import SwiftUI

struct ContentView: View {
    enum screens {
        case start, mid, final
    }
    @State var curScreen: screens = .start
    
    func changeScreen() {
        switch curScreen {
        case .start:
            curScreen = .mid
        case .mid:
            curScreen = .final
        case .final:
            return
        }
    }
    
    var body: some View {
        switch curScreen {
        case .start:
            ScreenView(callback: changeScreen, text: "Start")
                .transition(.slide)
        case .mid:
            ScreenView(callback: changeScreen, text: "Mid")
                .transition(.slide)
        case .final:
            ScreenView(callback: {}, text: "Final")
                .transition(.slide)
        }
    }
}

struct ScreenView: View {
    let callback: () -> ()
    let text: String
    var body: some View {
        Text(text)
            .padding()
            .frame(maxWidth: .infinity)
            .onTapGesture {
                withAnimation(.default) {
                    callback()
                }
            }
    }
}
noranraskin
  • 1,127
  • 8
  • 17

2 Answers2

34

@Asperi has already answered my question, but it didn't show up on google or stackoverflow when I searched for it, so here again for the algorithm:

How to reverse the slide transition in SwiftUI:

Taken from the Apple Developer Documentation:

static var slide: AnyTransition
// A transition that inserts by moving in from the leading edge, and removes by moving out towards the trailing edge.

Which can also be written as:

AnyTransition.asymmetric(
    insertion: .move(edge: .leading),
    removal: .move(edge: .trailing)
)

So to reverse the animation just flip insertion and removal and put that in your transition ViewModifier.

Since I needed to use it a few times I made an extension to AnyTransition so I can just call .transition(.backslide)

extension AnyTransition {
    static var backslide: AnyTransition {
        AnyTransition.asymmetric(
            insertion: .move(edge: .trailing),
            removal: .move(edge: .leading))}
}
noranraskin
  • 1,127
  • 8
  • 17
  • 1
    Yeah, that works when going only forward. Thanks. However in my example a user have a choice to go Next or to go Back on the same screen. In this case I set backslide transition or simple slide depending on what user's action. And it doesn't work right after the change. Seems `removal` transition was set previously and can't be changed on the go. Added the same comment to https://stackoverflow.com/questions/63782469/swiftui-how-to-have-next-and-back-animations# Maybe you have an idea how to deal with it. Thank you – Sander May 30 '23 at 22:59
1

for ios 16

 Group {
                    switch viewModel.selectedTab {
                        case .travel:
                            TravelSearchForm(viewModel: viewModel)
                        case .cars:
                            CarsSearchForm(viewModel: viewModel.carsForm)
                        case .rooms:
                            RoomsSearchForm(viewModel: viewModel.roomsForm)
                    }
                }
                .transition(.dynamicSlide(forward: $viewModel.tabTransitionDirectionIsForward))
                .animation(.default, value: viewModel.selectedTab)

extension AnyTransition {

    struct SlideModifier: ViewModifier {
        let width: CGFloat
        @Binding var forward: Bool

        func body(content: Content) -> some View {
            content
                .offset(x: (forward ? 1 : -1) * width)
        }
    }

    static func dynamicSlide(forward: Binding<Bool>) -> AnyTransition {
        .asymmetric(
            insertion: .modifier(
                active: SlideModifier(width: UIScreen.main.bounds.width, forward: forward),
                identity: SlideModifier(width: 0, forward: .constant(true))
            ),

            removal: .modifier(
                active: SlideModifier(width: -UIScreen.main.bounds.width, forward: forward),
                identity: SlideModifier(width: 0, forward: .constant(true))
            )
        )
    }
}
Dmih
  • 530
  • 6
  • 9