SwiftUI问答切换动画Bug:方向切换时首次动画异常
问答页面切换动画异常原因及修复
问题现象
- 点击Next按钮时动画正常:当前问题向左移出,下一题从右向左移入
- 点击Previous按钮时异常:当前问题仍向左移出(预期应为向右移出),上一题从左向右移入(表现正确)
- 从Previous切换回Next时,首次点击同样出现动画错位
异常原因
核心问题是状态更新顺序与动画触发时机不匹配:
SwiftUI的动画基于状态变化触发,当前代码中navigationDirection与currentQuestionIndex的更新同步执行,但动画仅监听currentQuestionIndex的变化。当修改navigationDirection后立刻更新currentQuestionIndex,SwiftUI在计算过渡动画时可能还未捕获到navigationDirection的新值,导致过渡逻辑使用了旧的方向参数,最终出现动画错位。
比如点击Previous时,代码先设置navigationDirection = .backward,随即修改currentQuestionIndex触发动画,但此时视图树还未更新navigationDirection的状态,过渡仍使用之前的.forward对应的slideRight(其移除动画为向左移出),所以当前问题依然左移。
修复方案
通过withAnimation将状态更新包裹为一个动画事务,确保SwiftUI同时处理navigationDirection和currentQuestionIndex的变化,让过渡逻辑能获取到正确的方向参数。
修改后的代码
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) .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() // 移除父视图的全局动画,改用withAnimation精确控制 } private func moveToNextQuestion() { if currentQuestionIndex < questions.count - 1 { // 用withAnimation包裹状态更新,确保动画事务包含所有状态变化 withAnimation(.easeInOut(duration: 1.0)) { navigationDirection = .forward currentQuestionIndex += 1 } } } private func moveToPreviousQuestion() { if currentQuestionIndex > 0 { withAnimation(.easeInOut(duration: 1.0)) { navigationDirection = .backward currentQuestionIndex -= 1 } } } }
关键修改点
- 移除父视图上的
.animation修饰符,避免全局动画与局部动画冲突 - 在
moveToNextQuestion和moveToPreviousQuestion方法中,用withAnimation包裹navigationDirection和currentQuestionIndex的更新,让两个状态变化属于同一个动画事务,确保过渡逻辑能获取到正确的方向参数
内容的提问来源于stack exchange,提问作者nirav dinmali
相关产品推荐
相关产品推荐

