SwiftUI多个垂直列表行动画失效修复及跨列表转场实现问询
解决方案
问题1 修复列表行基础动画
问题根源
- 自定义
bindingCalls的set实现错误:当前采用先全量删除对应状态的所有元素、再追加新数组的逻辑,会破坏SwiftUI Diff算法对元素身份的连续性识别,无法判定是元素更新/移动,只会判定为批量删除+批量新增,因此不会触发单行动画。 - 缺少显式的动画触发声明。
修复步骤
- 重写
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 } } } ) } }
- 给列表添加动画绑定,监听核心数组变化:
在ContentView的每个List上添加动画修饰符:
List(visitManager.inProgress) { $call in CallView(call: $call) } .animation(.easeInOut, value: visitManager.calls) // 其余两个列表添加同样的修饰符
- 点击状态变更时添加显式动画包裹:
修改CallView的点击事件:
.onTapGesture(count: 2) { withAnimation { call.state = .rejected } } .onTapGesture { withAnimation { call.state = .accepted } }
完成以上修改后,单列表内的插入、删除、重排序动画即可正常触发。
问题2 跨列表转场动画实现
最简便的方案是使用SwiftUI自带的matchedGeometryEffect修饰符,依托全局唯一的元素ID实现跨视图的平滑转场:
- 在
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 // ... 其余代码 }
- 给
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 } } } }
- 在
ContentView初始化CallView时传入命名空间:
List(visitManager.inProgress) { $call in CallView(call: $call, namespace: listTransitionNamespace) } // 其余两个列表同样传入命名空间
配置完成后,行元素在三个列表之间切换时,就会呈现平滑的位移、形变转场效果,和单个列表内的section移动效果一致。
内容的提问来源于stack exchange,提问作者Mojtaba Hosseini
相关产品推荐
相关产品推荐

