【发布时间】:2021-04-28 15:27:39
【问题描述】:
我正在尝试使用 Core Data 制作一个简单的笔记应用程序,但我遇到了 fetchedResultsController.object(at: indexPath) 和将数据传递到我的视图控制器的问题。使用 prepare(for segue: UIStoryboardSegue, sender: Any?)。
我的核心数据实体名为 Notes,具有以下属性
- dateStamp 整数 64
- noteName 字符串
- noteImage 数据
- noteDescription 字符串
但是,为了理解问题,我制作了单独的项目并将其限制为仅 noteName 和 dateStamp。
所以有 3 个 Controller 和 1 个 Helper 文件
- 主视图控制器
- 添加视图控制器
- DetailViewController
- EditViewController
- 日期助手
MasterView 是我的 UITableController 和我的 NSFetchedResultsControllerDelegate,所以代码如下...。
import UIKit
import CoreData
class MasterViewController: UITableViewController {
private let noteCreationTimeStamp : Int64 = Date().timetoSeconds()
var managedObjectContext: NSManagedObjectContext? {
return (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
}
var fetchedResultsController: NSFetchedResultsController<Notes>!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
override func viewDidLoad() {
super.viewDidLoad()
fetchFromCoreData()
navigationItem.leftBarButtonItem = editButtonItem
let addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(insertNewObject(_:)))
navigationItem.rightBarButtonItem = addButton
}
@objc
func insertNewObject(_ sender: Any) {
let addController = storyboard?.instantiateViewController(withIdentifier: "addNotes")
as! UINavigationController
self.present(addController, animated: true, completion: nil)
}
private func configureCells(cell: noterViewCell, withEvent note: Notes, indexPath: IndexPath) {
let record = fetchedResultsController.object(at: indexPath)
cell.noteName.text = record.noteName
cell.dateLabel.text = dateHelper.convertDate(date: Date.init(seconds: record.dateStamp))
if let noteName = record.value(forKey: "noteName") as? String, let dateTime = record.value(forKey: "dateStamp") as? Int64 {
cell.noteName.text = noteName
cell.dateLabel.text = dateHelper.convertDate(date: Date.init(seconds: dateTime))
}
}
override func numberOfSections(in tableView: UITableView) -> Int {
return fetchedResultsController.sections?.count ?? 0
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard let sections = self.fetchedResultsController?.sections else {
fatalError("No sections in fetchedResultsController")
}
let sectionInfo = sections[section]
return sectionInfo.numberOfObjects
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "noteCell", for: indexPath) as! noterViewCell
guard let object = self.fetchedResultsController?.object(at: indexPath) else {
fatalError("Attempt to configure cell without a managed object")
}
configureCells(cell: cell, withEvent: object, indexPath: indexPath)
cell.selectbutton.addTarget(self, action: #selector(selectNote(_:)), for: .touchUpInside)
return cell
}
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let context = fetchedResultsController.managedObjectContext
context.delete(fetchedResultsController.object(at: indexPath))
do {
try context.save()
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
func fetchFromCoreData() {
let fetchRequest: NSFetchRequest<Notes> = Notes.fetchRequest()
fetchRequest.fetchBatchSize = 800
let creationDateSortDescriptor = NSSortDescriptor(key: "dateStamp", ascending: false)
//let nameSortDescriptor = NSSortDescriptor(key: "noteName", ascending: false)
fetchRequest.sortDescriptors = [creationDateSortDescriptor]
let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.managedObjectContext!, sectionNameKeyPath: "dateStamp", cacheName: nil)
aFetchedResultsController.delegate = self
fetchedResultsController = aFetchedResultsController
do {
try fetchedResultsController.performFetch()
self.tableView.reloadData()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.localizedDescription), \(nserror.localizedFailureReason ?? "could not retrieve")")
//print("Could not save note to CoreData: \(error.localizedDescription)")
}
}
}
extension MasterViewController: NSFetchedResultsControllerDelegate {
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
switch type {
case .insert:
tableView.insertSections(IndexSet(integer: sectionIndex), with: .fade)
case .delete:
tableView.deleteSections(IndexSet(integer: sectionIndex), with: .fade)
default:
return
}
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch type {
case .insert:
tableView.insertRows(at: [newIndexPath!], with: .fade)
case .delete:
tableView.deleteRows(at: [indexPath!], with: .fade)
case .update:
tableView.reloadRows(at: [newIndexPath!], with: .fade)
if let updateIndexPath = newIndexPath {
let cell = self.tableView.cellForRow(at: updateIndexPath) as! noterViewCell
let event = anObject as! Notes
cell.dateLabel.text = event.noteName
cell.dateLabel.text = dateHelper.convertDate(date: Date.init(seconds: event.dateStamp))
}
case .move:
configureCells(cell: tableView.cellForRow(at: indexPath!) as! noterViewCell, withEvent: anObject as! Notes, indexPath: indexPath!)
tableView.moveRow(at: indexPath!, to: newIndexPath!)
@unknown default:
fatalError("Unresolved error")
}
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
}
}
我的 AddViewController
import UIKit
import CoreData
class AddViewController: UIViewController {
@IBOutlet var noteField: UITextField!
@IBOutlet var dateStamp: UILabel!
private let noteCreationTimeStamp : Int64 = Date().timetoSeconds()
let masterView = MasterViewController()
var isExisting: Bool = false
var note:Notes? = nil
var managedObjectContext: NSManagedObjectContext? {
return (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
}
override func viewDidLoad() {
super.viewDidLoad()
configureView()
}
var detailItem: Notes? {
didSet {
// Update the view.
configureView()
}
}
func configureView() {
if let detail = detailItem {
if let noteFieldLabel = noteField, let dateTime = dateStamp {
noteFieldLabel.text = detail.noteName
dateTime.text = dateHelper.convertDate(date: Date.init(seconds: detail.dateStamp))
}
}
}
@IBAction func saveImageButtonPressed(_ sender: Any) {
let context = self.managedObjectContext
let newEvent = Notes(context: context!)
newEvent.noteName = noteField.text
newEvent.dateStamp = noteCreationTimeStamp
do {
try context?.save()
} catch {
let error = error as NSError
fatalError("Unresolved error \(error), \(error.localizedDescription)")
}
let isPresentingMode = self.presentingViewController is UINavigationController
if isPresentingMode {
self.dismiss(animated: true, completion: nil)
}
else {
self.navigationController!.pushViewController(masterView, animated: true)
}
}
@IBAction func cancelView(_ sender: AnyObject) {
let isPresentingMode = self.presentingViewController is UINavigationController
if isPresentingMode {
self.dismiss(animated: true, completion: nil)
}
}
}
一切都很好,我的 saveImageButtonPressed 将数据保存到我的 managedObjectContext 并毫无问题地显示到 UITableview 上。
此代码用于我的 DetailViewController
import UIKit
import CoreData
class DetailViewController: UIViewController {
@IBOutlet var dateLabel: UILabel!
@IBOutlet var noteField: UILabel!
let masterView = MasterViewController()
var notes: Notes?
var myNotes = [Notes]()
var isExisting: Bool = false
var index = IndexPath()
private let noteCreationTimeStamp : Int64 = Date().timetoSeconds()
var managedObjectContext: NSManagedObjectContext? {
return (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
}
var detailItem: Notes? {
didSet {
// Update the view.
configureView()
}
}
func configureView() {
if let detail = detailItem {
if let noteFieldLabel = noteField, let dateStamp = dateLabel {
noteFieldLabel.text = detail.noteName
dateStamp.text = dateHelper.convertDate(date: Date.init(seconds: detail.dateStamp))
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
//fetchNote()
configureView()
}
@IBAction func cancelView(_ sender: AnyObject) {
let isPresentingMode = self.presentingViewController is UINavigationController
if isPresentingMode {
self.dismiss(animated: true, completion: nil)
}
}
//
func fetchNote() {
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Notes")
do {
myNotes = try managedObjectContext?.fetch(fetchRequest) as! [Notes]
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.localizedDescription,\(nserror.localizedFailureReason ?? "could not retrieve")")
}
}
//
}
所以我试图选择我的单元格以将保存的数据传递给我的 DetailViewController 但它没有选择。什么都没发生。我的 DetailViewController 中的 fetchNote 功能只是为了试用。
这是我准备的代码(对于 segue:UIStoryboardSegue,发件人:Any?)
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDetails" {
if let selectedIndexPath = tableView.indexPathForSelectedRow {
let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController
let objects = fetchedResultsController.object(at: selectedIndexPath)
controller.detailItem = objects
}
}
}
还有我的 tableView.didSelectRowAt: indexPath
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let cell = self.tableView.cellForRow(at: indexPath) as! noterViewCell
let event = fetchedResultsController.object(at: indexPath)
cell.dateLabel.text = event.noteName
cell.dateLabel.text = dateHelper.convertDate(date: Date.init(seconds: event.dateStamp))
//configureCells(cell: tableView.cellForRow(at: indexPath!) as! noterViewCell, withEvent: anObject as! Notes, indexPath: indexPath!)
self.performSegue(withIdentifier: "showDetails", sender: self)
}
我添加了 self.performSegue(withIdentifier: "showDetails", sender: self) 但 DetailViewController 只是空白。
为了进一步理解,我在 cellForRowAt indexPath 中添加了按钮:...
cell.selectbutton.addTarget(self, action: #selector(selectNote(_:)), for: .touchUpInside)
通过以下操作
@objc
func selectNote(_ sender: noterViewCell) {
let indexPath = IndexPath()
let objects = fetchedResultsController.object(at: indexPath) as Notes
let controller = DetailViewController()
controller.detailItem = objects
//let detailView = //storyboard?.instantiateViewController(withIdentifier: "viewNotes") as! //UINavigationController
//self.present(detailView, animated: true, completion: nil)
let detailViews = DetailViewController()
self.navigationController?.popToViewController(detailViews, animated: true)
}
这引发了“NSInvalidArgumentException”,原因:“索引 9223372036854775807 处没有部分”,这两种方法都尝试访问我的详细信息视图
这完全针对我的按钮操作@objc MasterViewController_selectNote(_:) 中的 fetchedResultsController.object
线程中还列出了 [NSFetchedResultsController objectAtIndexPath:]: 向下滚动我发现了另一个原因,“在 -performFetch: 之前无法访问获取的对象”以及“NSFetchedResultsController: 在索引 %lu 的部分中没有索引 %lu 的对象"
我在 viewDidLoad() 中调用了我的 fetchFromCoreData()……我取出了 performFetch,我直接将它粘贴到了 viewDidLoad() 中。如果我单击按钮,仍然会引发相同的错误。再次使用 prepareForSegue 选择空白视图。我再次尝试在我的 fetchFromCoreData() 函数中使用返回值。
我不确定我是否通过添加按钮添加了问题,但在所有情况下,我都尝试解决问题 prepareForSegue 它仍然传递一个空白视图。
我也尝试使用此函数验证 indexPath
func validateIndexPath(_ indexPath: IndexPath) -> Bool {
if let sections = self.fetchedResultsController.sections,
indexPath.section < sections.count {
if indexPath.row < sections[indexPath.section].numberOfObjects {
return true
}
}
return false
}
再次无济于事,在 cellForRowAt indexPath 上调用它,应用仍然以相同的 'NSInvalidArgumentException' 和 prepareForSegue 传递空白视图终止。
然后我在我的 tableView 中添加了我的 titleForHeaderInSection,它由月份和年份设置,当我将数据保存在 AddViewController 中时显示正常。我希望它可能会有所作为,但是……
func monthDate(string:String?) -> String? {
let date = Date()
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM"
let dateString = formatter.string(from: date)
let monthDate = formatter.date(from: dateString)
formatter.dateFormat = "MMM, yyyy"
let dateMonth = formatter.string(from: monthDate!)
return dateMonth
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
guard let sectionInfo = fetchedResultsController.sections?[section] else {
return nil
}
return monthDate(string: sectionInfo.name)
}
我真的不明白我哪里出错了......我在 IOS14 和 Xcode 12.3 上使用 iPhone 11 pro max 模拟器。(我大约一个月前升级了它确实挂了大约 4 或5 天,从那以后就有点问题了)
虽然我确信我可能错过了一些非常简单的事情,但我们将不胜感激。
另一方面,如果我理解正确,我知道 NSFetchedResultsController 不喜欢这篇博文中的空白部分……
http://www.iosnomad.com/blog/2014/8/6/swift-nsfetchedresultscontroller-trickery
如果这可能是问题所在,Swift 5 是否有更简单的解决方案?
编辑
在我的 tableview 中,我添加了 didSelectRowAt
print(fetchedResultsController.object(at: indexPath))
控制台输出我保存的数据,但它仍然没有将它传递给我的 DetailViewController。
我也尝试在我的 prepareForSegue 添加
print(fetchedResultsController.object(at: selectedIndexPath))
这没有返回保存的数据,并且在选择我的单元格后在控制台上打印出 tableView 中的 numberOfSections 后返回 0 个部分。但是当我打开我的应用程序时,数据仍然保存在我的单元格中并可以查看,并在我添加新注释时在控制台中打印出正确的部分编号。只有当我选择我的单元格以将保存的数据传递给我的 DetailViewController 时才会出现问题。
【问题讨论】:
-
在基于故事板的 UI 中,默认初始化程序
MasterViewController()创建控制器的一个新实例,该实例不是故事板中的实例。 -
感谢您的评论。您能否进一步解释一下,我需要做些什么来更改我的代码以便我可以选择我的单元格?
-
无论我在 selectNote 函数中所做的任何更改,它仍然会抛出错误,但只是为了澄清它的 prepareForSegue:sender: i need to work....
标签: swift uitableview core-data cell nsfetchedresultscontroller