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

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)无法正确跟踪数组元素的身份,导致拖拽排序时索引更新异常,仅前几项能正常响应。

修改步骤

  1. 给Item模型添加指向ToDo的反向关联,用@Relationship修饰明确关联关系
  2. 在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 13:12:34