首页 > 解决方案 > NSFetchResultController 没有更新 CollectionView 的单元格

问题描述

我正在尝试将 NSFetchController 与 CollectionView 一起使用。我的问题是当我更改 NSManagedObject 的属性值时,我的单元格没有正确更新。这是一个链接,可以更好地展示我在说什么。好的,所以如果您观看视频,您基本上可以看到该应用程序的功能。它有一堆英雄,当您单击一个单元格时,它会转到另一个视图控制器并显示图片、名称和发布者。现在你也能看到问题了,当我把名字改成“Amanda Doe”并保存时。它一开始就插入到了正确的位置,但单元格标签没有更新。

当我再次单击单元格时,它会在导航标题上显示 Amanda Doe,但单元格标签仍然相同。为了显示名称,我必须重新启动应用程序或单击同一单元格,然后再次单击保存。

这就是我的 NSFetchResultController 的样子。我按发布者名称排序,然后按英雄名称排序。它还具有基于发布者的多个部分。

private var blockOperations: [BlockOperation] = []

lazy var heroController: NSFetchedResultsController<Hero> = {
    let request: NSFetchRequest<Hero> = Hero.fetchRequest()
    let nameSort = NSSortDescriptor(key: "name", ascending: true)
    let publisherSort = NSSortDescriptor(key: "publisher.name", ascending: true)
    request.sortDescriptors = [publisherSort, nameSort]

    let context = CoreDataManager.shared.persistentContainer.viewContext
    let controller = NSFetchedResultsController(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: "publisher.name", cacheName: nil)
    controller.delegate = self

    do {
        try controller.performFetch()
    } catch let err {
        print("Unable to fetch heros from controller.", err)
    }

    return controller
}()

这里的代码基本上是我发现大多数人用来让 NSFetchResultController 与 CollectionView 一起工作的样板代码。

func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
    blockOperations.removeAll(keepingCapacity: false)
}

func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {

    let op: BlockOperation

    switch type {
    case .delete:
        op = BlockOperation { self.collectionView.deleteSections(IndexSet(integer: sectionIndex))}
        blockOperations.append(op)
    case .insert:
        op = BlockOperation { self.collectionView.insertSections(IndexSet(integer: sectionIndex))}
        blockOperations.append(op)
    case .move:
        break
    case .update:
        op = BlockOperation { self.collectionView.reloadSections(IndexSet(integer: sectionIndex))}
        blockOperations.append(op)
    @unknown default:
        fatalError()
    }
}

func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType,newIndexPath: IndexPath?) {

    let op: BlockOperation
    switch type {
    case .insert:
        guard let newIndexPath = newIndexPath else { return }
        op = BlockOperation { self.collectionView.insertItems(at: [newIndexPath]) }
    case .delete:
        guard let indexPath = indexPath else { return }
        op = BlockOperation { self.collectionView.deleteItems(at: [indexPath]) }
    case .move:
        guard let indexPath = indexPath,  let newIndexPath = newIndexPath else { return }
        op = BlockOperation { self.collectionView.moveItem(at: indexPath, to: newIndexPath) }
    case .update:
        guard let indexPath = indexPath else { return }
        op = BlockOperation { self.collectionView.reloadItems(at: [indexPath]) }
    @unknown default:
        fatalError()
    }

    blockOperations.append(op)
}

func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
    collectionView.performBatchUpdates({
        self.blockOperations.forEach { $0.start() }
    }, completion: { finished in
        self.blockOperations.removeAll(keepingCapacity: false)
    })
}

所以我错过了什么,是因为我错过了该部分的 .move 案例吗?我正在玩这个应用程序,发现单元格是否不移动或必须移动到新的索引路径,它工作正常。这是为什么?非常感谢任何帮助。谢谢。

编辑 1

在玩了更多代码之后,我遇到了另一个问题。问题 1 与您在视频中看到的一样。单元格没有正确更新,我不知道为什么。我尝试按照@pbasdf 的建议对其进行调试并发现了这一点。首先,当我将名称更改为“Amanda Doe”时,它会触发didChangeAnObject: .move两次,但不会触发didChangeAnObject: .update。当我再次单击同一个单元格并将名称更改为“Amanda Doee”时,它会调用didChangeAnObject: .update并使用正确的名称更新单元格。所以基本上如果调用了didChangeAnObject: .move案例,则单元格不会更新,并且如果调用了didChangeAnObject: .update案例,它会使用正确的名称更新单元格。

问题 2 是当我更改发布者名称时。所以现在的名字是“Amanda Doee”,出版商的名字是“ABC Studios”。现在,如果我只是将发布者名称更改为“ABC Studioss”,它应该将单元格移动到一个名为“ABC Studioss”的新部分,因为我的 NSFetchResultController。问题是它不会保持在相同的位置,而是调用didChangeAnObject: .update案例。

现在我找到了一种可行的方法。为了让它工作,我需要更改名称和发布者。如果我只是更改发布者,它将不起作用。所以我不得不把“Amanda Doee”改成“Amanda Doeee”,把“ABC Studios”改成“ABC Studioss”。这就是我更改名称和发布者并保存它时调用方法的方式。didChangeSectionInfo:.insertdidChangeAnObject:.movedidChangeSectionInfo:.insertdidChangeAnObject:.move。现在,如果我只是更改发布者的名称,这只会被称为didChangeAnObject: .update。我将不得不重新启动我的应用程序以查看带有“Amanda Doee”的名为“ABC Studioss”的新部分的更改。

所以我真的不知道发生了什么以及为什么会这样。

编辑 2

这是我在其他 VC 中用于保存编辑或创建新英雄的代码。

@objc private func handleSave() {
    let context = CoreDataManager.shared.persistentContainer.viewContext
    if let hero = hero {

        hero.name = nameTF.text ?? ""
        hero.publisher?.name = publisherTF.text

        do {
            try context.save()
        }
        catch let err {
            print("Unable to save hero edits.", err)
        }
    }
    else {
        print("Create new hero.")

        let hero = Hero(context: context)
        hero.name = nameTF.text
        let publisher = Publisher(context: context)
        publisher.name = publisherTF.text
        hero.publisher = publisher

        guard let imageData = heroImageView.image?.jpegData(compressionQuality: 1) else {
            try? context.save()
            return
        }

        let photo = Photo(context: context)
        photo.data = imageData
        hero.photo = photo

        try? context.save()
    }
    navigationController?.popViewController(animated: true)
}

标签: swiftcore-datauicollectionviewnsfetchedresultscontroller

解决方案


推荐阅读