列表视图中的核心数据项无法正确删除

问题描述 投票:0回答:1

在我的应用程序中,我有一个名为

LiftEventsView
的列表视图,用于显示电梯事件。电梯事件以
LiftEvent
的形式存储在核心数据中。

当我滑动删除电梯事件时,它不会立即从列表中消失。第一次滑动时,该行仍然可见,但

eventReps
eventWeight
formattedOneRepMax
值消失。如果我再刷一次,电梯事件就会被删除。

列表中的一项电梯活动:

enter image description here

第一次滑动后:

enter image description here

第二次滑动后:

enter image description here

我正在通过 Hacking with Swift+ 教程学习 SwiftUI,据我所知,我的代码的相关部分是相同的。我已经调试了2天了,检查又检查代码,但无法弄清楚。

struct LiftEventsView: View {
    @EnvironmentObject var dataController: DataController

    let viewModel = LiftEventRowViewModel(calculator: Calculator())

    var body: some View {
        List {
            ForEach(dataController.liftEventsForSelectedFilter()) { liftEvent in
                LiftEventRow(liftEvent: liftEvent, viewModel: viewModel)
            }
            .onDelete(perform: delete)
        }
        .navigationTitle("Lift Log")
    }

    func delete(_ offsets: IndexSet) {
        var events = dataController.liftEventsForSelectedFilter()

        Logger.liftEvents.debug(">>>User swiped to delete")
        Logger.coreData.debug("Events before deletion: \(events)")

        for offset in offsets {
            let item = events[offset]
            dataController.delete(item)
        }

        Logger.coreData.debug("Events after deletion: \(events)")
    }
}
struct LiftEventRow: View {
    @EnvironmentObject var dataController: DataController
    @ObservedObject var liftEvent: LiftEvent

    let viewModel: LiftEventRowViewModel

    var calculator = Calculator()

    var body: some View {
        HStack {
            VStack(alignment: .leading) {
                Text(liftEvent.eventLocalizedLiftName)
                    .font(.system(size: 18, weight: .semibold))
                    .foregroundColor(.black)

                Text("\(liftEvent.eventReps) reps @ \(liftEvent.eventWeight)" )
            }

            Spacer()

            VStack(alignment: .trailing) {
                if let weight = Double(liftEvent.eventWeight),
                   let reps = Int(liftEvent.eventReps) {
                    let formattedOneRepMax = viewModel.formattedOneRepMax(weight: weight, reps: reps, formulaName: liftEvent.eventFormulaName)

                    Text(formattedOneRepMax)
                        .font(.system(size: 18, weight: .semibold))
                        .foregroundColor(.black)
                }

                Text(liftEvent.eventFormulaName)
            }
        }
    }
}
class DataController: ObservableObject {
    let container: NSPersistentContainer

    @Published var selectedLiftEvent: LiftEvent?

    private var saveTask: Task<Void, Error>?

    // MARK: - Initialization

    init(inMemory: Bool = false) {

        container = NSPersistentContainer(name: "liftLog")

        if inMemory {
            container.persistentStoreDescriptions.first?.url = URL(filePath: "dev/null")
        }

        container.loadPersistentStores { storeDescription, error in
            if let error {
                fatalError("Fatal error loading store: \(error.localizedDescription)")
            }
        }

        let primer = CoreDataPrimer(managedObjectContext: self.container.viewContext)
        primer.seedCoreData()

        newLiftEvent()
    }

    func count<T>(for fetchRequest: NSFetchRequest<T>) -> Int {
        (try? container.viewContext.count(for: fetchRequest)) ?? 0
    }
    
    /// Creates a new LiftEvent and assigns it to @Published var selectedLiftEvent
    func newLiftEvent() {
        let liftEvent = LiftEvent(context: container.viewContext)
        liftEvent.uuid = NSUUID().uuidString
        liftEvent.date = .now
        liftEvent.formula = defaultFormula()
        liftEvent.lift = defaultLift()
        liftEvent.weightUnit = UserDefaults.unitsNotation().weightUnit


        self.selectedLiftEvent = liftEvent

        Logger.coreData.info("New lift event \(liftEvent.description) created")
        Logger.coreData.info("AFter new event created, selectedLiftEvent is \(self.selectedLiftEvent)")
    }

    /// Saves our Core Data context if there are changes. This silently ignores
    /// any errors caused by saving, but this should be fine because
    /// all our attributes are optional.
    func save() {
        saveTask?.cancel()

        if container.viewContext.hasChanges {
            try? container.viewContext.save()

            Logger.coreData.info("Context saved")
        }
    }

    func delete(_ object: NSManagedObject) {
        objectWillChange.send()
        container.viewContext.delete(object)
        save()

        Logger.coreData.debug("Deleted lift event \(object)")
    }

    func liftEventsForSelectedFilter() -> [LiftEvent] {
        let request = LiftEvent.fetchRequest()
        request.includesPendingChanges = false
        let allLiftEvents = (try? container.viewContext.fetch(request)) ?? []
        return allLiftEvents
    }

    func hasLiftEvents() -> Bool {
        var hasLiftEvents = false
        let fetchRequest = NSFetchRequest<NSNumber>(entityName: "LiftEvent")
        fetchRequest.includesPendingChanges = false
        fetchRequest.resultType = .countResultType

        do {
            let countResult = try container.viewContext.fetch(fetchRequest)
            hasLiftEvents = countResult.first!.intValue > 0

            Logger.coreData.info("There are \(countResult) lift events in the log")
        } catch let error {
            Logger.coreData.info("Error checking for lift events: \(error)")

        }

        return hasLiftEvents
    }
}
extension LiftEvent {
    var eventID: String {
        uuid ?? ""
    }

    var eventCreationDate: Date {
        date ?? .now
    }

    var eventLift: Lift {
        if let lift = lift {
            return lift
        } else {
            if let context = self.managedObjectContext {
                let newLift = Lift(context: context)
                return newLift
            } else {
                fatalError("Managed Object Context is nil.")
            }
        }
    }

    var eventLiftName: String {
        get { lift?.liftName ?? "None" }
    }

    var eventLocalizedLiftName: String {
        return NSLocalizedString(eventLiftName, tableName: "liftLogModel", comment: "")
    }

    var eventFormulaName: String {
        formula?.formulaName ?? "None"
    }

    var eventReps: String {
        get {
            guard let reps = repetitions, reps.intValue != 0 else {
                return ""
            }
            return String(describing: reps)
        }
        set { repetitions = NSNumber(value: Double(newValue) ?? 0) }
    }

    var eventWeight: String {
        get {
            guard let weight = weightLifted, weight.intValue != 0 else {
                return ""
            }
            return String(describing: weight)
        }
        set { weightLifted = NSNumber(value: Double(newValue) ?? 0) }
    }

    var eventWeightUnit: WeightUnit {
        get {
            if let rawValue = weightUnit {
                return WeightUnit(rawValue: rawValue) ?? .kilograms
            }
            return .kilograms
        }
        set {
            weightUnit = newValue.rawValue
        }
    }
}

记录消息


Context saved

There are [1] lift events in the log

>>>User swiped to delete

Events before deletion: [<LiftEvent: 0x600002129220> (entity: LiftEvent; id: 0x83602b006ad823c5 <x-coredata://DF552D0A-935D-4873-9114-CFB3C5CAAC8F/LiftEvent/p33>; data: {
    date = "2024-05-16 17:43:07 +0000";
    formula = "0x83602b006e3823f5 <x-coredata://DF552D0A-935D-4873-9114-CFB3C5CAAC8F/Formula/p6>";
    lift = "0x83602b006fb823e5 <x-coredata://DF552D0A-935D-4873-9114-CFB3C5CAAC8F/Lift/p10>";
    repetitions = 3;
    uuid = "9B7F9CFA-1E3B-45E8-99B7-4D74A3428FD9";
    weightLifted = 205;
    weightUnit = kg;
})]

Context saved

Deleted lift event <LiftEvent: 0x600002129220> (entity: LiftEvent; id: 0x83602b006ad823c5 <x-coredata://DF552D0A-935D-4873-9114-CFB3C5CAAC8F/LiftEvent/p33>; data: <fault>)

Events after deletion: [<LiftEvent: 0x600002129220> (entity: LiftEvent; id: 0x83602b006ad823c5 <x-coredata://DF552D0A-935D-4873-9114-CFB3C5CAAC8F/LiftEvent/p33>; data: <fault>)]

There are [1] lift events in the log

>>>User swiped to delete

Events before deletion: [<LiftEvent: 0x60000213ee90> (entity: LiftEvent; id: 0x83602b006ab823c5 <x-coredata://DF552D0A-935D-4873-9114-CFB3C5CAAC8F/LiftEvent/p34>; data: {
    date = "2024-05-16 17:43:14 +0000";
    formula = "0x83602b006e3823f5 <x-coredata://DF552D0A-935D-4873-9114-CFB3C5CAAC8F/Formula/p6>";
    lift = "0x83602b006fb823e5 <x-coredata://DF552D0A-935D-4873-9114-CFB3C5CAAC8F/Lift/p10>";
    repetitions = 0;
    uuid = "AFAB249F-0F14-4088-A46A-73A4060BCB4F";
    weightLifted = 0;
    weightUnit = kg;
})]

Context saved

Deleted lift event <LiftEvent: 0x60000213ee90> (entity: LiftEvent; id: 0x83602b006ab823c5 <x-coredata://DF552D0A-935D-4873-9114-CFB3C5CAAC8F/LiftEvent/p34>; data: <fault>)

Events after deletion: [<LiftEvent: 0x60000213ee90> (entity: LiftEvent; id: 0x83602b006ab823c5 <x-coredata://DF552D0A-935D-4873-9114-CFB3C5CAAC8F/LiftEvent/p34>; data: <fault>)]

There are [0] lift events in the log
swiftui core-data swiftui-list
1个回答
0
投票

我终于弄清楚是什么导致了这种奇怪的行为。

应用程序启动时,会创建一个新的电梯事件。因为此时尚未保存,所以它被标记为临时对象。我的提取请求中有

request.includesPendingChanges = false
,因此临时对象不会出现在日志中。

当用户保存该电梯事件时,系统会自动创建新事件,以便他们可以执行另一次计算。 此时,上下文中存在一个已保存的电梯事件和新的临时电梯事件。

然后,如果我查看日志并滑动以删除已保存的电梯事件,则会将其删除并保存上下文。此保存还保存了临时提升事件,使其不再是临时的。因此,当日志视图刷新时,现在会出现此提升事件。

func delete(_ object: NSManagedObject) {
    objectWillChange.send()
    container.viewContext.delete(object)
        
    save()
}

现在的问题是,如果我从

save()
函数中删除
delete
,视图不会更新并删除已删除的电梯事件。

© www.soinside.com 2019 - 2024. All rights reserved.