animationswiftuiuiviewanimationtransition

Animation Bug: When changing directions, the view only animates incorrectly once


I need to create an animation for previous and next questions. The current question should move out and next/prev question should come in.

It functions nicely if you keep clicking Next.

  1. Current question moves out towards left
  2. Next question appears from right to left

But if I switch the direction to previous,

  1. Current question moves out towards left (should move out towards right)
  2. next question comes in from left to right (which is correct).

When I navigate from previous to next, the first click always results in a similar disparity.

Why is this so?

import SwiftUI
 
struct Question {
    let id: Int
    let text: String
}

extension AnyTransition {
    static var slideRight: AnyTransition {
        let insertion = AnyTransition.move(edge: .trailing)
        let removal = AnyTransition.move(edge: .leading)
        return .asymmetric(insertion: insertion, removal: removal)
    }
    
    static var slideLeft: AnyTransition {
        let insertion = AnyTransition.move(edge: .leading)
        let removal = AnyTransition.move(edge: .trailing)
        return .asymmetric(insertion: insertion, removal: removal)
    }
}

enum NavigationDirection {
    case forward, backward
}

struct QuizView: View {
    let questions = [
        Question(id: 1, text: "Qustion 1: 1"),
        Question(id: 2, text: "Qustion 2: to"),
        Question(id: 3, text: "Qustion 3: III"),
        Question(id: 4, text: "Qustion 4: 4444"),
        Question(id: 5, text: "Qustion 5: 12345"),
        Question(id: 6, text: "Qustion 6: FFFFFF")
    ]
    
    @State private var currentQuestionIndex = 0
    @State private var navigationDirection: NavigationDirection = .forward
    
    var body: some View {
        VStack(spacing: 20) {
            Text(questions[currentQuestionIndex].text)
                .id(questions[currentQuestionIndex].id) // Important for transition
                .transition(navigationDirection == .forward ? .slideRight : .slideLeft)
                .frame(maxWidth: .infinity, maxHeight: .infinity)
            
            HStack {
                Button("Previous") {
                    moveToPreviousQuestion()
                }
                .disabled(currentQuestionIndex == 0)
                
                Spacer()
                
                Button("Next") {
                    moveToNextQuestion()
                }
                .disabled(currentQuestionIndex == questions.count - 1)
            }
        }
        .padding()
        .animation(.easeInOut(duration: 1.0), value: currentQuestionIndex)

    }
    
    private func moveToNextQuestion() {
        if currentQuestionIndex < questions.count - 1 {
                navigationDirection = .forward
                currentQuestionIndex += 1
        }
    }
    
    private func moveToPreviousQuestion() {
        if currentQuestionIndex > 0 {
                navigationDirection = .backward
                currentQuestionIndex -= 1
        }
    }
}

Solution

  • It performs nicely when there is a slight delay in the variable update:

    private func moveToNextQuestion() {
        navigationDirection = .forward
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
            if currentQuestionIndex < questions.count - 1 {
                currentQuestionIndex += 1
            }
        }
    }
    
    private func moveToPreviousQuestion() {
        navigationDirection = .backward
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
            if currentQuestionIndex > 0 {
                currentQuestionIndex -= 1
            }
        }
    }