You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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
            }
        }
    }
}

关键修改点

  1. 移除父视图上的.animation修饰符,避免全局动画与局部动画冲突
  2. 在moveToNextQuestion和moveToPreviousQuestion方法中,用withAnimation包裹navigationDirection和currentQuestionIndex的更新,让两个状态变化属于同一个动画事务,确保过渡逻辑能获取到正确的方向参数

内容的提问来源于stack exchange,提问作者nirav dinmali

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 11:31:05