SwiftUI remembers the "old" view in order to perform a transition to the "new" view. When you reverse direction, the outgoing view is still associated with a transition going in the (now) wrong direction. My fix is to immediately change the direction when you press the button. This causes body() to be re-invoked, but no transition is visible because the view ID of the question has not changed.
Subsequently, a Task alters the view ID, which causes a transition from the old question (now associated with a removal transition in the new direction) to the new question.
I think this solution smells a little, because it is imperatively driving the UI, and because I have no idea when the Task closures will actually be invoked. No doubt after the Button's action closure, but are they guaranteed to be invoked before any subsequent UI action? I don't know.
I changed QuizView to ContentView to get it to compile in my test app.
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)
}
}
struct ContentView: View {
let questions = [
Question(id: 1, text: "11111111111"),
Question(id: 2, text: "222222222222222222222222"),
Question(id: 3, text: "3333333333333333333"),
Question(id: 4, text: "444444444444444444444444"),
Question(id: 5, text: "55555555555555555555"),
Question(id: 6, text: "6666666666666666666666666")
]
@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 {
if navigationDirection == .backward {
navigationDirection = .forward
}
Task {
currentQuestionIndex += 1
}
}
}
private func moveToPreviousQuestion() {
if currentQuestionIndex > 0 {
if navigationDirection == .forward {
navigationDirection = .backward
}
Task {
currentQuestionIndex -= 1
}
}
}
}
enum NavigationDirection {
case forward, backward
}