如何使用 SwiftData 从列表中删除子项目?

Nig*_*l-W 4 ios swift swift-data swiftui

我是 SwiftUI 的新手,对于我的第一个应用程序,我决定尝试 SwiftData,因为我不想稍后将其转换为 SwiftData。我的应用程序有计时器(MyTimer),每个计时器都有一个名称和几个重置,每个重置都有一个日期和一个文本字符串。我希望用户能够选择 MyTimer 并查看其重置列表,然后从那里能够编辑或删除其中的任何一个。因此,我的第一个视图允许创建 MyTimer 并向其添加重置。第二个视图显示所选 MyTimer 的重置列表。

我遇到的问题是,当我删除多个重置时,重置数组似乎没有更新,如果您从列表的同一行删除第二个重置,它会尝试删除已删除的重置,并且我收到错误:线程 1:致命错误:此方法不准备对脱离其托管对象上下文的支持数据进行操作。关系需要通过 objectID 进行深度复制才能发挥作用。

这是应用程序数据模型文件的代码:

import Foundation
import SwiftData
import SwiftUI

@Model
final class MyTimer {
    var id: String
    var name: String
    
    @Relationship(deleteRule: .cascade)
    var resets: [Reset]
    
    @Transient
    var sortedResets: [Reset] {
        print(resets.count)
        return self.resets.sorted { $0.date > $1.date }
    }

    init(_ name: String = "",
         startDate: Date = .now) {

        let id = UUID().uuidString
        self.id = id
        self.name = name
        self.resets = [Reset(text: "Started On", date: startDate)]
    }
}

@Model
class Reset: Identifiable {
    var id: String
    var text: String
    var date: Date

    init(text: String, date: Date) {
        self.id = UUID().uuidString
        self.text = text
        self.date = date
    }
}

Run Code Online (Sandbox Code Playgroud)

应用程序文件:

import SwiftUI
import SwiftData

@main
struct SwiftDataHabitTestApp: App {

    let container = try? ModelContainer(for: MyTimer.self, Reset.self)


    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .modelContainer(container!)
    }
}
Run Code Online (Sandbox Code Playgroud)

内容查看:

import SwiftUI
import SwiftData

struct ContentView: View {

    @Environment(\.modelContext) private var modelContext
    @State var timerName: String = ""
    @State var resetViewShowing = false
    @State var timerForResetView: MyTimer?
    @Query(sort: \MyTimer.name) var allTimers: [MyTimer]

    var body: some View {
        ZStack {
            VStack {
                TextField(text: $timerName, prompt: Text("Name your Timer")) {
                    Text("Name:")
                }
                Button {
                    let newTimer = MyTimer(timerName)
                    modelContext.insert(newTimer)
                } label: {
                    Text("Create Timer")
                }
                List {
                    ForEach(allTimers){ dispTimer in
                        Text(dispTimer.name)
                        Button {
                            let newReset = Reset(text: "New REset", date: .now)
                            dispTimer.resets.append(newReset)
                            try? modelContext.save()
                        } label: {
                            Text("Reset")
                        }
                        Button {
                            resetViewShowing = true
                            timerForResetView = dispTimer
                        } label: {
                            Text("View Resets")
                        }

                    }
                    .onDelete(perform: { indexSet in
                        indexSet.forEach { index in
                            modelContext.delete(allTimers[index])
                        }
                    })
                }

            }
            .padding()
            if resetViewShowing {
                ResetListView(dispTimer: timerForResetView!)
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

重置列表视图

import SwiftUI
import SwiftData

struct ResetListView: View {

    @Environment(\.modelContext) private var modelContext
    @State var dispTimer: MyTimer

    var body: some View {
//  var resetList: [Reset] = dispTimer.resets.sorted { $0.date > $1.date }
        List {
            ForEach(dispTimer.resets) {
                Text($0.text)
            }
            .onDelete(perform: { indexSet in
                indexSet.forEach { index in
                    print("index: \(index)")
                    print("length of resets: \(dispTimer.resets.count)")
                    modelContext.delete(dispTimer.resets[index])
                    do {
                        try modelContext.save() <<---- Error: Thread 1: Fatal error: This method isn't prepared to operate on a backing data that's unmoored from its managed object context. Relationships will need to be deep copied by objectID for that to work.
                    } catch {
                        print("error saving")
                    }
                }
            })
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我尝试了几种使用重置排序数组的方法,因为我希望它按日期顺序显示在列表中,将它们排序到视图中的 var 中或在 MyTimer 中使用计算属性,但随后出现不同的错误:Thread 1: EXC_BREAKPOINT (code=1, subcode=0x1a8a4fad8)出现在重置日期属性的吸气剂中我也尝试过使用 .swipeActions 而不是使用 .ondelete ,但这似乎并不能解决问题。

Pau*_*w11 5

错误消息中有对您的问题的提示 -

此方法不准备对脱离其托管对象上下文的支持数据进行操作。关系需要通过 objectID 进行深度复制才能正常工作

那么,我们如何获得那个“真实”的对象呢?

您可以使用它persistentIdentifier从托管对象上下文中获取它。

请注意,除了删除对象外,您还需要显式地将其从对象的resets数组中删除MyTimer

将您的delete代码更改为:

for index in IndexSet {
    let objectId = timer.resets[index].persistentModelID
    let reset = modelContext.model(for: objectId)
    modelContext.delete(reset)
}
timer.resets.remove(atOffsets: offsets)
do {
    try modelContext.save()
} catch {
    print("Error saving context \(error)")
}
Run Code Online (Sandbox Code Playgroud)

会解决你的问题。