Swift+Core Data无法正确保存并在Table View中展示数据
Hey,我仔细看了你提供的代码,发现几个关键问题导致保存后TableView里出现NaN值,咱们一步步来修复:
1. Record类的Core Data属性声明错了!
你现在的Record类直接声明了存储属性,还默认设成了.nan,但Core Data的实体属性必须用@NSManaged修饰,这样才能和数据模型里的属性绑定,不然Core Data根本不会持久化这些值,读取的时候自然还是初始的NaN。
修改后的Record类应该是这样:
import Foundation import CoreData class Record: NSManagedObject { @NSManaged var minimumValue: Float @NSManaged var averageValue: Float @NSManaged var maximumValue: Float }
别忘了检查你的
.xcdatamodeld文件里的History实体,要确保已经添加了这三个Float类型的属性,并且把实体的类名设置为Record哦。
2. saveNewRecord方法在做无用功,还搞乱了数据
你在prepare(for:sender:)里已经创建了record对象并插入到上下文了,但saveNewRecord里又重新创建了一个空的NSManagedObject数组,这完全是多余的,反而导致你之前赋值的record没被正确处理,最终存到Core Data里的是空值,读取时就变成了NaN。
把saveNewRecord改成这样就行:
private func saveNewRecord(record: Record) { let appDelegate = UIApplication.shared.delegate as! AppDelegate let context = appDelegate.persistentContainer.viewContext do { try context.save() } catch let error { print("保存数据失败:\(error.localizedDescription)") } }
3. TableView的数据逻辑可以更靠谱
你现在手动维护recordsArray,不如直接用NSFetchedResultsController来自动获取和刷新数据,这样数据变更时TableView能自动更新,还不容易出错。
先修改RecordTableViewController里的FRC:
private lazy var fetchedResultsController: NSFetchedResultsController<Record> = { // 用自动生成的fetchRequest,比手动写更安全 let fetchRequest = Record.fetchRequest() // 加个排序规则,比如按最小值升序,不然数据顺序会乱 let sortDescriptor = NSSortDescriptor(key: "minimumValue", ascending: true) fetchRequest.sortDescriptors = [sortDescriptor] let appDelegate = UIApplication.shared.delegate as! AppDelegate let managedObjectContext = appDelegate.persistentContainer.viewContext let frc = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: managedObjectContext, sectionNameKeyPath: nil, cacheName: nil) frc.delegate = self return frc }()
然后在viewDidLoad里执行fetch:
override func viewDidLoad() { super.viewDidLoad() let swipe = UISwipeGestureRecognizer(target: self, action: #selector(swipeRight(_:))) swipe.direction = .right self.view.addGestureRecognizer(swipe) // 执行FRC的fetch操作,获取初始数据 do { try fetchedResultsController.performFetch() } catch { print("获取数据失败:\(error.localizedDescription)") } }
再修改TableView的数据源方法,直接用FRC的数据:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return fetchedResultsController.sections?[section].numberOfObjects ?? 0 } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! RecordCell let record = fetchedResultsController.object(at: indexPath) // 额外加个安全处理,如果真的出现NaN,显示0.0代替 cell.minimumValueLabel.text = record.minimumValue.isNaN ? "0.0" : String(format: "%.2f", record.minimumValue) cell.averageValueLabel.text = record.averageValue.isNaN ? "0.0" : String(format: "%.2f", record.averageValue) cell.maximumValueLabel.text = record.maximumValue.isNaN ? "0.0" : String(format: "%.2f", record.maximumValue) return cell }
4. 文本转Float的地方加个安全防护
在prepare(for:sender:)里,从Label转Float的时候,最好先处理一下空文本的情况,避免因为Label为空导致转换失败:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if segue.identifier == "saveRecord" { let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext // 用Record.entity()来创建对象,比手动写实体名更安全 let record = Record(entity: Record.entity(), insertInto: context) // 安全转换文本为Float,空文本的话默认设为0.0 record.minimumValue = Float(minDbLabel.text ?? "") ?? 0.0 record.averageValue = Float(averageDbLabel.text ?? "") ?? 0.0 record.maximumValue = Float(maximumDbLabel.text ?? "") ?? 0.0 saveNewRecord(record: record) // 现在不需要手动维护recordsArray了,FRC会自动获取最新数据 } }
最后总结一下
最核心的问题就是Record类的属性没有正确绑定Core Data实体,导致数据根本没被正确持久化,再加上重复创建Core Data对象的操作,才出现了NaN值。按照上面的步骤修改后,应该就能正常保存和展示数据啦!
内容的提问来源于stack exchange,提问作者cratos

