6

I made sample app with 3 pages and "Next", "Back" buttons.

enter image description here

When I click "Next" animation is perfect: the old content goes to the left and new content comes from the right.

However, I'm struggling to make "Back" animation to work nice simultaneously.

Goal: when clicking "Back" old content should go to the right and new content should come from the left. (animation for "Back" should be reverse to animation for "Next")

Any idea how to accomplish this?

Below is perfectly working transition for "Next"

struct ContentView: View {
    @State var page: Int = 0
    
    var body: some View {
        
        VStack {
            HStack {
                Button(action: { withAnimation() { self.page = self.page - 1 } }) {
                    Text("Back")
                }
                
                Spacer()
                
                Button(action: { withAnimation() { self.page = self.page + 1 }}) {
                    Text("Next")
                }
            }
            Spacer()
            
            if page == 0 {
                PageView(name: "First page", color: .brown)
                    .transition(AnyTransition.asymmetric(insertion: .move(edge: .trailing), removal: .move(edge: .leading)))
            } else if page == 1 {
                PageView(name: "Second page", color: .systemGreen)
                    .transition(AnyTransition.asymmetric(insertion: .move(edge: .trailing), removal: .move(edge: .leading)))
            } else if page == 2 {
                PageView(name: "Third page", color: .systemBlue)
                    .transition(AnyTransition.asymmetric(insertion: .move(edge: .trailing), removal: .move(edge: .leading)))
            }
        }
    }
}

struct PageView: View {
    var name: String
    var color: UIColor
    var body: some View {
        HStack {
            Spacer()
            Text(name)
            Spacer()
        }
        .padding()
        .padding(.vertical, 50)
        .background(Color(color))
    }
}
Markon
  • 741
  • 8
  • 23

2 Answers2

6

You need to reverse transition when navigating back.

Here is possible approach (also made transition in one place and corrected animation to work everywhere, including Preview).

Tested with Xcode 12 / iOS 14.

demo

struct ContentView: View {
    @State var page: Int = 0

    @State private var isBack = false   // << reverse flag (not animatable)
    var body: some View {

        VStack {
            HStack {
                Button(action: {
                    self.isBack = true
                    self.page = self.page - 1
                }) {
                    Text("Back")
                }

                Spacer()

                Button(action: {
                    self.isBack = false
                    self.page = self.page + 1
                }) {
                    Text("Next")
                }
            }
            Spacer()

            Group {
                if page == 0 {
                    PageView(name: "First page", color: .brown)
                } else if page == 1 {
                    PageView(name: "Second page", color: .systemGreen)
                } else if page == 2 {
                    PageView(name: "Third page", color: .systemBlue)
                }
            }.transition(AnyTransition.asymmetric(
                insertion:.move(edge: isBack ? .leading : .trailing),
                removal: .move(edge: isBack ? .trailing : .leading))
            )
            .animation(.default, value: self.page)   // << animate here by value
        }
    }
}
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Asperi
  • 228,894
  • 20
  • 464
  • 690
  • 1
    This doesn't work on iOS 15 – Morpheus Nov 10 '22 at 10:30
  • Yes, agree with previous comment. On iOS 16 if try to tap Next 3 times and then Back 3 times it works like in the video. But if I try to tap Next and then Back, then the latter animation is broken. It would be nice to get any help with the fix. – Sander May 30 '23 at 22:50
1

Works for me in ios 16

Group {
                switch viewModel.selectedTab {
                    case .travel:
                        view1
                    case .cars:
                        view2
                    case .rooms:
                        view3
                }
            }
            .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 {
        return .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