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

SwiftUI多个垂直列表行动画失效修复及跨列表转场实现问询

解决方案

问题1 修复列表行基础动画

问题根源

  • 自定义bindingCalls的set实现错误:当前采用先全量删除对应状态的所有元素、再追加新数组的逻辑,会破坏SwiftUI Diff算法对元素身份的连续性识别,无法判定是元素更新/移动,只会判定为批量删除+批量新增,因此不会触发单行动画。
  • 缺少显式的动画触发声明。

修复步骤

  1. 重写bindingCalls的set逻辑,改为差异更新,保留元素身份:
extension CallManager {
    func bindingCalls(for state: Call.State) -> Binding<[Call]> {
        Binding<[Call]>(
            get: { self.calls.filter { $0.state == state } },
            set: { newValue in
                // 同步列表内的重排序变更
                let stateIds = newValue.map(\.id)
                self.calls.sort { a, b in
                    guard let indexA = stateIds.firstIndex(of: a.id),
                          let indexB = stateIds.firstIndex(of: b.id) else {
                        return a.state.hashValue < b.state.hashValue
                    }
                    return indexA < indexB
                }
                // 同步元素的状态变更
                for newCall in newValue {
                    if let index = self.calls.firstIndex(where: { $0.id == newCall.id }) {
                        self.calls[index] = newCall
                    }
                }
            }
        )
    }
}
  1. 给列表添加动画绑定,监听核心数组变化:
    在ContentView的每个List上添加动画修饰符:
List(visitManager.inProgress) { $call in
    CallView(call: $call)
}
.animation(.easeInOut, value: visitManager.calls)
// 其余两个列表添加同样的修饰符
  1. 点击状态变更时添加显式动画包裹:
    修改CallView的点击事件:
.onTapGesture(count: 2) { 
    withAnimation { call.state = .rejected }
}
.onTapGesture { 
    withAnimation { call.state = .accepted }
}

完成以上修改后,单列表内的插入、删除、重排序动画即可正常触发。

问题2 跨列表转场动画实现

最简便的方案是使用SwiftUI自带的matchedGeometryEffect修饰符,依托全局唯一的元素ID实现跨视图的平滑转场:

  1. 在ContentView中声明动画命名空间:
struct ContentView: View {
    @StateObject var visitManager = CallManager(visits: [
        Call(state: .inProgress),
        Call(state: .accepted),
        Call(state: .accepted),
        Call(state: .inProgress),
        Call(state: .inProgress),
        Call(state: .rejected)
    ])
    // 新增动画命名空间
    @Namespace private var listTransitionNamespace
    // ... 其余代码
}
  1. 给CallView新增命名空间参数并添加匹配几何效果:
struct CallView: View & Identifiable {
    @Binding var call: Call
    // 新增命名空间入参
    var namespace: Namespace.ID
    var id: UUID { call.id }

    var body: some View {
        Text(call.id.uuidString.prefix(15))
            .foregroundColor(call.state.color)
            // 新增匹配几何效果修饰符
            .matchedGeometryEffect(id: call.id, in: namespace)
            .onTapGesture(count: 2) { 
                withAnimation { call.state = .rejected }
            }
            .onTapGesture { 
                withAnimation { call.state = .accepted }
            }
    }
}
  1. 在ContentView初始化CallView时传入命名空间:
List(visitManager.inProgress) { $call in
    CallView(call: $call, namespace: listTransitionNamespace)
}
// 其余两个列表同样传入命名空间

配置完成后,行元素在三个列表之间切换时,就会呈现平滑的位移、形变转场效果,和单个列表内的section移动效果一致。

内容的提问来源于stack exchange,提问作者Mojtaba Hosseini

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.12 11:57:01