【问题标题】:Reordering Cells with UICollectionViewDiffableDataSource and NSFetchedResultsController使用 UICollectionViewDiffableDataSource 和 NSFetchedResultsController 重新排序单元格
【发布时间】:2020-01-21 13:36:27
【问题描述】:

我正在使用UICollectionViewDiffableDataSourceNSFetchedResultsController 在我的UIViewController 中填充我的UICollectionView

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

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

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

当我申请dataSource?.apply(snapshot, animatingDifferences: true) 时,单元格会立即切换回位置。如果我设置animatingDifferences: false 它可以工作,但所有单元格都会重新加载可见

这里有什么最佳实践,如何在UICollectionViewDiffableDataSourceNSFetchedResultsController 上实现单元格重新排序?

这是我提到的方法:

// 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)
    }
}

【问题讨论】:

  • 你找到解决方法了吗?

标签: ios swift uicollectionview nsfetchedresultscontroller ios13


【解决方案1】:

不需要子类。从 iOS 14.0 开始,UICollectionViewDiffableDataSource 支持reordering handlers 你可以实现。

let data_source = UICollectionViewDiffableDataSource<MySection, MyModelObject>( collectionView: collection_view, cellProvider:
{
    [weak self] (collection_view, index_path, video) -> UICollectionViewCell? in
        
    let cell = collection_view.dequeueReusableCell( withReuseIdentifier: "cell", for: index_path ) as! MyCollectionViewCell

    if let self = self
    {
        //setModel() is my own method to update the view in MyCollectionViewCell
        cell.setModel( self.my_model_objects[index_path.item] )
    }

    return cell
})

// Allow every item to be reordered as long as there's 2 or more
diffable_data_source.reorderingHandlers.canReorderItem = 
{ 
    item in 
    my_model_objects.count >= 2 return true 
}

//Update your model objects before the reorder occurs. 
//You can also use didReorder, but it might be useful to have your
//model objects in the correct order before dequeueReusableCell() is 
//called so you can update the cell's view with the correct model object.
diffable_data_source.reorderingHandlers.willReorder = 
{ 
    [weak self] transaction in
    
    guard let self = self else { return }
    
    self.my_model_objects = transaction.finalSnapshot.itemIdentifiers
}

【讨论】:

  • 基于此示例代码,重新排序处理程序似乎仅在列表单元格显示重新排序附件时才适用于列表布局?如何让它与自定义组合布局一起使用?
【解决方案2】:

我对同一问题的解决方案是将 UICollectionViewDiffableDataSource 子类化并在子类中实现 canMoveItemAt 方法以回答 true。 如果 .ended 的 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
    }

}

【讨论】:

    猜你喜欢
    • 2017-06-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多