SwiftUI List使用.move时部分子项无法拖拽排序问题解决
问题
基于Xcode默认Items示例修改,引入两个存在一对多关联的SwiftData模型,实现查询视图与详情视图后,发现详情视图中仅顶部两个子项可通过.move正常拖拽排序,其余子项无法移动,该现象随ToDo的子项数量变化。目标系统iOS 17.5,问题在预览、模拟器及真机上均出现,需调整代码实现所有子项可拖拽。
最小复现代码
Models
@Model final class ToDo { var timestamp: Date var items: [Item] init(timestamp: Date) { self.timestamp = timestamp self.items = [] } } @Model final class Item { var timestamp: Date var done: Bool init(timestamp: Date, done: Bool) { self.timestamp = timestamp self.done = done } }
ItemListView(问题所在)
struct ItemListView: View { @Bindable var todo: ToDo var body: some View { List { ForEach($todo.items) { $item in Text(item.timestamp.description) } .onMove { indexSet, offset in todo.items.move(fromOffsets: indexSet, toOffset: offset) } } } }
ContentView
struct ContentView: View { @Environment(\.modelContext) private var modelContext @Query var items: [ToDo] var body: some View { NavigationSplitView { List { ForEach(items) { item in NavigationLink { ItemListView(todo: item) } label: { Text(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard)) } } .onDelete(perform: deleteItems) } .toolbar { ToolbarItem(placement: .navigationBarTrailing) { EditButton() } ToolbarItem { Button(action: addItem) { Label("Add Item", systemImage: "plus") } } } } detail: { Text("Select an item") } } private func addItem() { withAnimation { let newItem = ToDo(timestamp: Date()) modelContext.insert(newItem) } } private func deleteItems(offsets: IndexSet) { withAnimation { for index in offsets { modelContext.delete(items[index]) } } } }
APP
@main struct ListProjectApp: App { var sharedModelContainer: ModelContainer = { let schema = Schema([ ToDo.self, ]) let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false) do { return try ModelContainer(for: schema, configurations: [modelConfiguration]) } catch { fatalError("Could not create ModelContainer: \(error)") } }() var body: some Scene { WindowGroup { ContentView() } .modelContainer(ToDoContainer.create()) } } actor ToDoContainer { @MainActor static func create() -> ModelContainer { let schema = Schema([ ToDo.self, Item.self ]) let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false) let container = try! ModelContainer(for: schema, configurations: [modelConfiguration]) let todo = ToDo(timestamp: Date()) container.mainContext.insert(todo) let item1 = Item(timestamp: Date(), done: false) let item2 = Item(timestamp: Date(), done: false) let item3 = Item(timestamp: Date(), done: false) todo.items.append(item1) todo.items.append(item2) todo.items.append(item3) return container } }
解决方案
问题原因
SwiftData中,一对多关联的数组若未显式配置反向关联,ForEach($todo.items)无法正确跟踪数组元素的身份,导致拖拽排序时索引更新异常,仅前几项能正常响应。
修改步骤
- 给
Item模型添加指向ToDo的反向关联,用@Relationship修饰明确关联关系 - 在
onMove闭包中,将数组移动操作包裹在withAnimation中,确保视图正确刷新
修改后的代码
Models(关键修改)
@Model final class ToDo { var timestamp: Date // 显式标记一对多关联,指定反向属性 @Relationship(deleteRule: .cascade, inverse: \Item.todo) var items: [Item] init(timestamp: Date) { self.timestamp = timestamp self.items = [] } } @Model final class Item { var timestamp: Date var done: Bool // 添加反向关联到ToDo var todo: ToDo? init(timestamp: Date, done: Bool) { self.timestamp = timestamp self.done = done } }
ItemListView(关键修改)
struct ItemListView: View { @Bindable var todo: ToDo @Environment(\.modelContext) private var modelContext var body: some View { List { ForEach($todo.items) { $item in Text(item.timestamp.description) } .onMove { indexSet, offset in withAnimation { todo.items.move(fromOffsets: indexSet, toOffset: offset) // 显式保存上下文确保变更生效 try? modelContext.save() } } } } }
额外说明
- 添加反向关联后,SwiftData能正确识别数组中每个Item的所属ToDo,避免拖拽时元素身份丢失
- 在
ToDoContainer创建Item时,可显式设置item.todo = todo,进一步确保关联关系正确建立:
let item1 = Item(timestamp: Date(), done: false) item1.todo = todo todo.items.append(item1) // 同理处理item2、item3
内容的提问来源于stack exchange,提问作者ThomasDeLange
相关产品推荐
相关产品推荐

