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

SwiftUI中ForEach内多视图动画适配非废弃语法问题

解决方案

核心思路是让布局变化的触发范围覆盖整个LazyVStack,确保所有卡片的位移都能被动画捕获。以下是两种可行方案:

方案1:使用withAnimation包裹状态切换

直接在卡片的点击回调中用withAnimation包裹状态变更操作,无需在视图上添加.animation(_:value:)修饰符,系统会自动将整个布局变化纳入动画范围。

修改ListCard的onTapGesture部分:

.onTapGesture {
    withAnimation(.easeInOut(duration: 0.3)) {
        isExpanded.toggle()
    }
}

同时移除卡片VStack上的.animation(.easeInOut(duration: 0.3), value: isExpanded),箭头的旋转动画会被withAnimation自动覆盖,无需单独设置。

修改后的完整ListCard代码:

struct ListCard: View {
    let entry: listItem
    @State private var isExpanded: Bool = false
    
    var body: some View {
        VStack(alignment: .leading, spacing: 12) {
            HStack {
                VStack(alignment: .leading, spacing: 4) {
                    Text("\(entry.status)")
                        .font(.headline)
                    
                    Text(entry.date)
                        .foregroundColor(.secondary)
                        .allowsHitTesting(false)
                        
                }
                
                Spacer()
                
                Image(systemName: "chevron.down")
                    .rotationEffect(.degrees(isExpanded ? 180 : 0))
            }
            if isExpanded {
                Text("\(entry.statusDescription)")
                    .font(.body)
                    .lineSpacing(4)
                    .transition(.opacity)
            }
        }
        .padding()
        .background(.gray.opacity(0.10))
        .cornerRadius(8)
        .onTapGesture {
            withAnimation(.easeInOut(duration: 0.3)) {
                isExpanded.toggle()
            }
        }
    }
}

此方案简单直接,适配单卡片独立展开的场景,完全符合原需求的动画效果。

方案2:父视图统一管理所有卡片状态(支持多卡片展开)

如果需要支持同时展开多个卡片,可将所有卡片的展开状态提升到父视图中管理,然后在LazyVStack上添加动画修饰符,监听状态集合的变化。

  1. 修改ContentView,添加状态字典并初始化:
struct ContentView: View {
    let listOfItems: [listItem]
    @State private var expandedStates: [String: Bool] = [:]
    
    init() {
        let item1 = listItem(status: "In Progress", date: "2023/04/17 10:30", statusDescription: "Item processing started.", caseId: "CASE123")
        let item2 = listItem(status: "Completed", date: "2023/04/16 14:45", statusDescription: "Item successfully processed.", caseId: "CASE124")
        let item3 = listItem(status: "Pending", date: "2023/04/18 09:00", statusDescription: "Item awaiting processing.", caseId: "CASE125")
        let item4 = listItem(status: "Failed", date: "2023/04/15 18:10", statusDescription: "Item processing failed due to an error.", caseId: "CASE126")
        listOfItems = [item1, item2, item3, item4]
        listOfItems.forEach { expandedStates[$0.caseId] = false }
    }

    var body: some View {
        LazyVStack(spacing: 8) {
            ForEach(listOfItems, id: \.self) { listItem in
                ListCard(
                    entry: listItem,
                    isExpanded: expandedStates[listItem.caseId] ?? false,
                    onToggle: {
                        expandedStates[listItem.caseId]?.toggle()
                    }
                )
            }
        }
        .animation(.easeInOut(duration: 0.3), value: expandedStates)
        .frame(width: 300)
    }
}
  1. 修改ListCard,改为接收外部状态和回调:
struct ListCard: View {
    let entry: listItem
    let isExpanded: Bool
    let onToggle: () -> Void
    
    var body: some View {
        VStack(alignment: .leading, spacing: 12) {
            HStack {
                VStack(alignment: .leading, spacing: 4) {
                    Text("\(entry.status)")
                        .font(.headline)
                    
                    Text(entry.date)
                        .foregroundColor(.secondary)
                        .allowsHitTesting(false)
                        
                }
                
                Spacer()
                
                Image(systemName: "chevron.down")
                    .rotationEffect(.degrees(isExpanded ? 180 : 0))
            }
            if isExpanded {
                Text("\(entry.statusDescription)")
                    .font(.body)
                    .lineSpacing(4)
                    .transition(.opacity)
            }
        }
        .padding()
        .background(.gray.opacity(0.10))
        .cornerRadius(8)
        .onTapGesture {
            onToggle()
        }
    }
}

此方案中,父视图的LazyVStack会监听expandedStates的变化,只要任意卡片状态改变,整个布局都会触发动画,所有卡片的位移都会平滑过渡。


内容的提问来源于stack exchange,提问作者Mete Polat

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 08:47:02