使用 UICollectionViewDiffableDataSource 和 NSFetchedResultsController 重新排序单元格

mrt*_*lst 8 nsfetchedresultscontroller ios uicollectionview swift ios13

我正在使用UICollectionViewDiffableDataSource和 aNSFetchedResultsController来填充UICollectionView我的UIViewController.

为了添加重新排序单元格的能力,我添加了一个UILongPressGestureRecognizer和子类UICollectionViewDiffableDataSource以便使用它的canMoveItemAt:moveItemAt:方法。

对单元格重新排序时,会发生以下情况:

  1. moveItemAt: 被调用,我更新对象位置属性并保存 MOC
  2. controllerDidChangeContent:NSFetchedResultsControllerDelegate被调用,我从当前的 fetchedObjects 创建一个新快照并应用它。

当我应用dataSource?.apply(snapshot, animatingDifferences: true)单元格时,立即切换位置。如果我设置animatingDifferences: false它有效,但所有单元格都可见地重新加载。

这里有什么最佳实践,如何在 aUICollectionViewDiffableDataSource和 a上实现单元格重新排序NSFetchedResultsController

以下是我提到的方法:

// ViewController 
func createSnapshot(animated: Bool = true) {
    var snapshot = NSDiffableDataSourceSnapshot<Int, Favorite>()
    snapshot.appendSections([0])
    snapshot.appendItems(provider.fetchedResultsController.fetchedObjects ?? [])
    dataSource?.apply(snapshot, animatingDifferences: animated)
}

// NSFetchedResultsControllerDelegate
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
    createSnapshot(animated: false)
}

// Subclassed UICollectionViewDiffableDataSource
override func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
    provider.moveFavorite(from: sourceIndexPath.row, to: destinationIndexPath.row)
}

// Actual cell moving in a provider class
public func moveFavorite(from source: Int, to destination: Int) {
    guard let favorites = fetchedResultsController.fetchedObjects else { return }
    if source < destination {
        let partialObjects = favorites.filter({ $0.position <= destination && $0.position >= source })

        for object in partialObjects {
            object.position -= 1
        }

        let movedFavorite = partialObjects.first
        movedFavorite?.position = Int64(destination)
    }
    else {
        let partialObjects = favorites.filter({ $0.position >= destination && $0.position <= source })

        for object in partialObjects {
            object.position += 1
        }

        let movedFavorite = partialObjects.last
        movedFavorite?.position = Int64(destination)
    }
    do {
        try coreDataHandler.mainContext.save()
    } catch let error as NSError {
        print(error.localizedDescription)
    }
}
Run Code Online (Sandbox Code Playgroud)

Wil*_*ser 1

我对同一问题的解决方案是子类化 UICollectionViewDiffableDataSource 并在子类中实现 canMoveItemAt 方法以回答 true。如果 .ending 的 longPressAction 情况做了三件事,那么动画似乎对我来说效果很好:

  1. 更新模型

  2. 调用 dateSource.collectionView(..moveItemAt:..)

  3. 运行你的 dataSource.apply

还必须实现其他常用的拖动行为方法,看起来您已经完成了。供其他人参考 - 这些方法在 UICollectionView 的“交互式重新排序项目”部分中有详细记录。https://developer.apple.com/documentation/uikit/uicollectionview

class PGLDiffableDataSource: UICollectionViewDiffableDataSource<Int, Int> {
    override func collectionView(_ collectionView: UICollectionView, canMoveItemAt indexPath: IndexPath) -> Bool {
        return true
    }

}
Run Code Online (Sandbox Code Playgroud)