在我的应用程序中,我有一个名为
LiftEventsView
的列表视图,用于显示电梯事件。电梯事件以 LiftEvent
的形式存储在核心数据中。
当我滑动删除电梯事件时,它不会立即从列表中消失。第一次滑动时,该行仍然可见,但
eventReps
、eventWeight
和 formattedOneRepMax
值消失。如果我再刷一次,电梯事件就会被删除。
列表中的一项电梯活动:
第一次滑动后:
第二次滑动后:
我正在通过 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
我终于弄清楚是什么导致了这种奇怪的行为。
应用程序启动时,会创建一个新的电梯事件。因为此时尚未保存,所以它被标记为临时对象。我的提取请求中有
request.includesPendingChanges = false
,因此临时对象不会出现在日志中。
当用户保存该电梯事件时,系统会自动创建新事件,以便他们可以执行另一次计算。 此时,上下文中存在一个已保存的电梯事件和新的临时电梯事件。
然后,如果我查看日志并滑动以删除已保存的电梯事件,则会将其删除并保存上下文。此保存还保存了临时提升事件,使其不再是临时的。因此,当日志视图刷新时,现在会出现此提升事件。
func delete(_ object: NSManagedObject) {
objectWillChange.send()
container.viewContext.delete(object)
save()
}
现在的问题是,如果我从
save()
函数中删除 delete
,视图不会更新并删除已删除的电梯事件。