То, что я пытаюсь сделать:
Я пытаюсь научиться объединять основные данные с NSFetchedResultsController и UITableViewDiffableDataSource, чтобы создать простой пользовательский интерфейс таблицы, который плавно обрабатывает добавления и удаления в таблицу элементов в Core Data.
Я начал с создания кода основных данных, затем добавил TableView, сначала подключил его с помощью ручных выборок, а затем перешел в NSFetchedResultsController, со "старым" поведением пакетного обновления, теперь я перемещаю этот код на использование DiffableDataSource.
Ошибка:
У меня есть две функции для удаления элементов в моем основное хранилище данных. Либо через секретное меню (доступное через встряхивание), которое содержит кнопку удаления всех, либо через класс c проведите пальцем, чтобы удалить жест.
Использование кнопки удаления всего никогда не вызывает ошибки, но проведите пальцем, чтобы удалить работает примерно в 50% случаев.
Сообщение об ошибке выглядит следующим образом:
Fatal error: Unable to delete note: Error Domain=NSCocoaErrorDomain Code=134030 "An error occurred while saving." UserInfo={NSAffectedObjectsErrorKey=(
"<Note: 0x6000013af930> (entity: Note; id: 0x6000030f9ba0 <x-coredata:///Note/t82D95BE5-DDAE-4684-B19E-4CDA842DF89A2>; data: {\n creation = nil;\n text = nil;\n})"
)}: file /Users/philip/Git/Noter/Noter/Noter/Note+CoreDataClass.swift, line 58
Изменить: Я сузил условия ошибки дальше.
Ошибка возникает только тогда, когда я пытаюсь удалить последний созданный объект.
То есть создать новую заметку, а затем удалить ту же заметку.
Если я создайте две заметки и удалите созданную первой, я не получаю ошибки. После этого удаления я могу аналогичным образом go ввести и удалить вторую заметку без каких-либо ошибок.
Аналогичным образом, если я создам заметку, остановлю симулятор, а затем перезапущу его, загрузив заметку из Core Data, я Я могу удалить эту заметку без каких-либо проблем.
Что я пробовал:
Исходя из того факта, что ошибка возникает только при использовании смахивания для удаления, я Я думаю, что проблема может быть каким-то образом связана с UITableViewDiffableDataSource, у которого все еще есть документация ниже номинала.
Я попытался проверить свою базу данных Core Data с помощью программы просмотра SQL (Liya), база данных, похоже, имеет записи, которые я ожидаю, и я вижу, что они создаются и удаляются правильно при создании 1 или 1000 записей с помощью меню отладки, удаление всех записей с помощью меню отладки также выглядит правильным.
Я включил -com.apple.CoreData.SQLDebug 1
аргумент, чтобы увидеть результат SQL. Опять же, кажется, что вставки и удаления работают нормально. За исключением случаев, когда возникает ошибка.
Я также пытался использовать отладчик и решить проблему, хотя по какой-то странной причине мне кажется, что я использую команду frame variable
lldb в моей точке останова (начало функция tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool
) вызывает сбой точки останова, и код просто переходит к ошибке.
Код:
Это отрывок из того, что Я считаю, что это соответствующий код, полный исходный код также доступен по адресу https://github.com/Hanse00/Noter/tree/master/Noter/Noter. Буду признателен за любую помощь в понимании проблемы.
ViewController.swift
import UIKit
import CoreData
// MARK: - UITableViewDiffableDataSource
class NoteDataSource<SectionIdentifierType>: UITableViewDiffableDataSource<SectionIdentifierType, NSManagedObjectID> where SectionIdentifierType: Hashable {
var container: NSPersistentContainer!
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 {
guard let objectID = itemIdentifier(for: indexPath) else {
fatalError("Unable to find note for indexPath: \(indexPath)")
}
guard let note = container.viewContext.object(with: objectID) as? Note else {
fatalError("Could not load note for id: \(objectID)")
}
Note.delete(note: note, from: container)
}
}
}
// MARK: - ViewController
class ViewController: UIViewController {
lazy var formatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .short
return formatter
}()
lazy var fetchedResultsController: NSFetchedResultsController<Note> = {
let controller = NSFetchedResultsController(fetchRequest: Note.sortedFetchRequest(), managedObjectContext: self.container.viewContext, sectionNameKeyPath: nil, cacheName: nil)
controller.delegate = self
return controller
}()
var container: NSPersistentContainer!
var dataSource: NoteDataSource<Section>!
@IBOutlet var tableView: UITableView!
enum Section: CaseIterable {
case main
}
override func viewDidLoad() {
super.viewDidLoad()
configureDataSource()
tableView.delegate = self
}
override func viewDidAppear(_ animated: Bool) {
do {
try fetchedResultsController.performFetch()
} catch {
fatalError("Failed to fetch entities: \(error)")
}
}
func configureDataSource() {
dataSource = NoteDataSource(tableView: tableView, cellProvider: { (tableView: UITableView, indexPath: IndexPath, objectID: NSManagedObjectID) -> UITableViewCell? in
let cell = tableView.dequeueReusableCell(withIdentifier: "NoteCell", for: indexPath)
guard let note = self.container.viewContext.object(with: objectID) as? Note else {
fatalError("Could not load note for id: \(objectID)")
}
cell.textLabel?.text = note.text
cell.detailTextLabel?.text = self.formatter.string(from: note.creation)
return cell
})
dataSource.container = container
}
// MARK: User Interaction
override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
if motion == .motionShake {
addRandomNotePrompt()
}
}
func addRandomNotePrompt() {
let alert = UIAlertController(title: "Add Random Note", message: nil, preferredStyle: .actionSheet)
let addAction = UIAlertAction(title: "Add a Note", style: .default) { (action) in
Note.createRandomNote(in: self.container)
}
let add1000Action = UIAlertAction(title: "Add 1000 Notes", style: .default) { (action) in
Note.createRandomNotes(notes: 1000, in: self.container)
}
let deleteAction = UIAlertAction(title: "Delete all Notes", style: .destructive) { (action) in
Note.deleteAll(in: self.container)
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
alert.addAction(addAction)
alert.addAction(add1000Action)
alert.addAction(deleteAction)
alert.addAction(cancelAction)
present(alert, animated: true)
}
}
// MARK: - UITableViewDelegate
extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
}
// MARK: - NSFetchedResultsControllerDelegate
extension ViewController: NSFetchedResultsControllerDelegate {
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) {
dataSource.apply(snapshot as NSDiffableDataSourceSnapshot<Section, NSManagedObjectID>, animatingDifferences: true)
}
}
Note + CoreDataClass.swift
import Foundation
import CoreData
@objc(Note)
public class Note: NSManagedObject {
public class func fetchRequest() -> NSFetchRequest<Note> {
return NSFetchRequest<Note>(entityName: "Note")
}
public class func sortedFetchRequest() -> NSFetchRequest<Note> {
let request: NSFetchRequest<Note> = fetchRequest()
let sort = NSSortDescriptor(key: "creation", ascending: false)
request.sortDescriptors = [sort]
return request
}
public class func createRandomNote(in container: NSPersistentContainer) {
let thingsILike = ["Trains", "Food", "to party party!", "Swift"]
let text = "I like \(thingsILike.randomElement()!)"
let dayOffset = Int.random(in: -365...365)
let hourOffset = Int.random(in: -12...12)
let dateOffsetDays = Calendar.current.date(byAdding: .day, value: dayOffset, to: Date())!
let date = Calendar.current.date(byAdding: .hour, value: hourOffset, to: dateOffsetDays)!
let note = Note(context: container.viewContext)
note.creation = date
note.text = text
do {
try container.viewContext.save()
} catch {
fatalError("Unable to save: \(error)")
}
}
public class func createRandomNotes(notes count: Int, in container: NSPersistentContainer) {
for _ in 1...count {
createRandomNote(in: container)
}
}
public class func delete(note: Note, from container: NSPersistentContainer) {
do {
container.viewContext.delete(note)
try container.viewContext.save()
} catch {
fatalError("Unable to delete note: \(error)")
}
}
public class func deleteAll(in container: NSPersistentContainer) {
do {
let notes = try container.viewContext.fetch(fetchRequest()) as! [Note]
for note in notes {
delete(note: note, from: container)
}
} catch {
fatalError("Unable to delete notes: \(error)")
}
}
}
Примечание + CoreDataProperties.swift
import Foundation
import CoreData
extension Note {
@NSManaged public var text: String
@NSManaged public var creation: Date
}
Спасибо!