使用CoreData与@FetchRequest时,如何实现SwiftUI List排序变化动画?
解决CoreData列表基于Bool属性排序变化的动画问题
问题分析
你通过@FetchRequest获取CoreData对象列表,点击行项修改对象的visited(Bool类型)属性后,列表会根据该属性重新排序,但整个过程没有动画——即使给@FetchRequest设置了animation参数,给List或RowView添加动画也无效。
解决方案
核心问题在于修改并保存CoreData上下文的操作没有被纳入动画块,导致SwiftUI无法捕捉到排序变化的动画过程。只需要在修改属性和保存上下文的代码外层包裹withAnimation,同时配合@FetchRequest的动画设置即可实现平滑的排序动画。
修改后的代码如下:
1. 调整RowView的按钮动作
struct RowView: View { @ObservedObject var place: Place var body: some View { Button { // 将修改和保存操作包裹在withAnimation中 withAnimation { place.visit() try? DataController.shared.container.viewContext.save() } } label: { HStack { Text(place.wrappedName) Spacer() place.visited ? Image(systemName: "checkmark.circle.fill") : Image(systemName: "checkmark.circle") } } } }
2. 保留ListDetailView的@FetchRequest动画设置
你的@FetchRequest初始化时已经指定了animation: .default,这个设置可以保留,它会确保FetchResults更新时,列表的重排动画按照指定的动画曲线执行:
struct ListDetailView: View { let list: PlaceList @FetchRequest var places: FetchedResults<Place> init(list: PlaceList) { self.list = list _places = .init( entity: Place.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Place.visited, ascending: true)], predicate: NSPredicate(format: "list = %@", list), animation: .default // 保留这个动画设置 ) } var body: some View { VStack { Text(self.list.wrappedName) .font(.headline) List { ForEach(places) { place in RowView(place: place) } } .listStyle(.plain) } } }
原理说明
withAnimation会将闭包内的状态变化(包括CoreData上下文的修改)标记为需要动画过渡,SwiftUI会自动计算前后状态的差异,并应用动画效果。@FetchRequest的animation参数指定了当FetchResults发生变化时,列表项重排所使用的动画类型,两者配合就能实现排序变化的平滑动画。
内容的提问来源于stack exchange,提问作者bchards
相关产品推荐
相关产品推荐

