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

SwiftData:如何从@Query属性获取Binding适配List组件?

解决SwiftData中List无法直接绑定@Query数组的问题

问题原因

@Query返回的数组由SwiftData查询引擎管理,其投影值$persons不支持直接修改数组结构(添加/删除元素),而List(data:, editActions:, rowContent:)要求数据源支持通过Binding修改数组,因此直接传入$persons会报错。


方案一:使用ForEach + 手动处理编辑动作(推荐)

这种方式更贴合SwiftData的设计逻辑,直接通过modelContext操作数据,无需额外包装:

import SwiftUI
import SwiftData

struct BindableTest: View {
    @Environment(\.modelContext) var modelContext
    @Query var persons: [Person]
    
    var body: some View {
        VStack {
            Button("Add") {
                let person = Person(name: "test", age: 12)
                modelContext.insert(person)
            }
            
            List {
                ForEach($persons) { $person in
                    VStack(alignment: .leading) {
                        Text(person.name)
                        Text(person.age.formatted())
                    }
                }
                .onDelete(perform: deletePersons)
                .onMove(perform: movePersons)
            }
            .toolbar {
                EditButton()
            }
        }
    }
    
    private func deletePersons(at offsets: IndexSet) {
        offsets.forEach { index in
            modelContext.delete(persons[index])
        }
    }
    
    private func movePersons(from source: IndexSet, to destination: Int) {
        // 若需要持久化排序,需给Person添加排序字段(如position: Int)
        // 示例:
        // var sortedPersons = persons.sorted(by: { $0.position < $1.position })
        // sortedPersons.move(fromOffsets: source, toOffset: destination)
        // sortedPersons.enumerated().forEach { index, person in
        //     person.position = index
        // }
    }
}

方案二:自定义Bindable数据源(适配List的editActions参数)

如果坚持使用List(data:, editActions:, rowContent:),可以通过ObservableObject包装数据源,同步SwiftData的查询结果与编辑动作:

import SwiftUI
import SwiftData

class PersonsDataSource: ObservableObject {
    @Published var items: [Person]
    private let modelContext: ModelContext
    
    init(items: [Person], modelContext: ModelContext) {
        self.items = items
        self.modelContext = modelContext
    }
    
    var bindableItems: Binding<[Person]> { $items }
    
    func handleDelete(_ indices: IndexSet) {
        indices.map { items[$0] }.forEach(modelContext.delete)
        items.remove(atOffsets: indices)
    }
    
    func handleMove(_ source: IndexSet, _ destination: Int) {
        items.move(fromOffsets: source, toOffset: destination)
        // 更新排序字段(需Person模型包含position属性)
        items.enumerated().forEach { index, person in
            person.position = index
        }
    }
}

struct BindableTest: View {
    @Environment(\.modelContext) var modelContext
    @Query var persons: [Person]
    @StateObject private var dataSource: PersonsDataSource
    
    init() {
        let container = try! ModelContainer(for: Person.self)
        _dataSource = StateObject(wrappedValue: PersonsDataSource(items: [], modelContext: container.mainContext))
    }
    
    var body: some View {
        VStack {
            Button("Add") {
                let person = Person(name: "test", age: 12, position: dataSource.items.count)
                modelContext.insert(person)
            }
            
            List(dataSource.bindableItems, editActions: .all) { $person in
                VStack(alignment: .leading) {
                    Text(person.name)
                    Text(person.age.formatted())
                }
            }
            .onChange(of: persons) { newPersons in
                dataSource.items = newPersons.sorted(by: { $0.position < $1.position })
            }
            .onReceive(dataSource.$items) { _ in
                try? modelContext.save()
            }
        }
    }
}

注意:方案二需要给Person模型添加排序字段(如position: Int),否则移动操作无法持久化。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 09:35:31