Ios UICollectionViewDropDelegate和DiffableDataSource

Ios UICollectionViewDropDelegate和DiffableDataSource,ios,swift,datasource,Ios,Swift,Datasource,我试图用DiffableDataSource(在iOS 13中引入)实现UICollectionViewDropDelegate 我已经实现了UICollectionViewDragDelegate,它工作得很好 我设置了collectionView.dropDelegate=self 我实现了func collectionView(collectionView:UICollectionView,performDropWith coordinator:UICollectionViewDropC

我试图用DiffableDataSource(在iOS 13中引入)实现
UICollectionViewDropDelegate

  • 我已经实现了
    UICollectionViewDragDelegate
    ,它工作得很好
  • 我设置了collectionView.dropDelegate=self
  • 我实现了
    func collectionView(collectionView:UICollectionView,performDropWith coordinator:UICollectionViewDropCoordinator)
    (您甚至可以在方法为空的情况下尝试)
应用程序将生成并运行,但一旦您开始拖动单元格,应用程序将立即崩溃,并显示以下消息:

由于未捕获的异常“NSInternalInconsistencyException”而终止应用程序,原因:“当充当UICollectionView的数据源时,必须通过UICollectionViewDiffableDataSource API更新UICollectionView:请不要直接在UICollectionView上调用变异API

因此,这种方法似乎试图直接修改UICollectionView(可能是在移动项目时移动单元格?),我不知道如何绕过这种行为


想法?

这是一个相当简单的解决方法。从UICollectionViewDropDelegate函数performDropWith开始。请注意,我只需要一个.move和no.copy,但您只需要添加一个copyItems()方法,类似于下面的reorderItems()方法:

func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) {
   var destinationIndexPath: IndexPath
   if let indexPath = coordinator.destinationIndexPath {
      destinationIndexPath = indexPath
   } else {
      let section = collectionView.numberOfSections - 1
      let row = collectionView.numberOfItems(inSection: section)
      destinationIndexPath = IndexPath(row: row, section: section)
   }
   switch coordinator.proposal.operation {
   case .move:
      self.reorderItems(coordinator: coordinator, destinationIndexPath: destinationIndexPath, collectionView: collectionView)
      break
   case .copy:
      // Not copying between collections so this block not needed.
      return
   default:
      return
   }
}
然后我们使用函数reorderItems()来处理collectionView中的实际更改。需要明确的一点是,快照(NSDiffableDataSourceSnapshot)和数据源(UICollectionViewDiffableDataSource)都是类变量

/// reorderItems method
/// This method moves a cell from the sourceIndexPath to the destinationIndexPath within the same UICollectionView
///
/// - Parameters:
///   - coordinator: UICollectionViewDropCoordinator obtained in performDropWith
///   - destinationIndexPath: IndexPath where user dropped the element.
///   - collectionView: UICollectionView object where reordering is done.
private func reorderItems(coordinator: UICollectionViewDropCoordinator, destinationIndexPath: IndexPath, collectionView: UICollectionView) {
   let items = coordinator.items
   if items.count == 1, let item = items.first, let sourceIndexPath = item.sourceIndexPath {
      var destIndexPath = destinationIndexPath
      if destIndexPath.row >= collectionView.numberOfItems(inSection: destIndexPath.section) {
         destIndexPath.row = collectionView.numberOfItems(inSection: destIndexPath.section) - 1
      }

      /// Since my collectionView data is attributed to a Firebase.storage set of data, this is where I write my changes back to the store.

      snapshot.moveItem(dataSource.itemIdentifier(for: sourceIndexPath)!, beforeItem: dataSource.itemIdentifier(for: destinationIndexPath)!)
      dataSource.apply(snapshot, animatingDifference: true)
      coordinator.drop(items.first!.dragItem, toItemAt: destIndexPath)
   }
}

这是一个相当容易解决的问题。从UICollectionViewDropDelegate函数performDropWith开始。请注意,我只需要一个.move和no.copy,但您只需要添加一个copyItems()方法,类似于下面的reorderItems()方法:

func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) {
   var destinationIndexPath: IndexPath
   if let indexPath = coordinator.destinationIndexPath {
      destinationIndexPath = indexPath
   } else {
      let section = collectionView.numberOfSections - 1
      let row = collectionView.numberOfItems(inSection: section)
      destinationIndexPath = IndexPath(row: row, section: section)
   }
   switch coordinator.proposal.operation {
   case .move:
      self.reorderItems(coordinator: coordinator, destinationIndexPath: destinationIndexPath, collectionView: collectionView)
      break
   case .copy:
      // Not copying between collections so this block not needed.
      return
   default:
      return
   }
}
然后我们使用函数reorderItems()来处理collectionView中的实际更改。需要明确的一点是,快照(NSDiffableDataSourceSnapshot)和数据源(UICollectionViewDiffableDataSource)都是类变量

/// reorderItems method
/// This method moves a cell from the sourceIndexPath to the destinationIndexPath within the same UICollectionView
///
/// - Parameters:
///   - coordinator: UICollectionViewDropCoordinator obtained in performDropWith
///   - destinationIndexPath: IndexPath where user dropped the element.
///   - collectionView: UICollectionView object where reordering is done.
private func reorderItems(coordinator: UICollectionViewDropCoordinator, destinationIndexPath: IndexPath, collectionView: UICollectionView) {
   let items = coordinator.items
   if items.count == 1, let item = items.first, let sourceIndexPath = item.sourceIndexPath {
      var destIndexPath = destinationIndexPath
      if destIndexPath.row >= collectionView.numberOfItems(inSection: destIndexPath.section) {
         destIndexPath.row = collectionView.numberOfItems(inSection: destIndexPath.section) - 1
      }

      /// Since my collectionView data is attributed to a Firebase.storage set of data, this is where I write my changes back to the store.

      snapshot.moveItem(dataSource.itemIdentifier(for: sourceIndexPath)!, beforeItem: dataSource.itemIdentifier(for: destinationIndexPath)!)
      dataSource.apply(snapshot, animatingDifference: true)
      coordinator.drop(items.first!.dragItem, toItemAt: destIndexPath)
   }
}

如果您正在使用collectionView.PerformBatChUpdate()进行更改以更新collectionView,则此操作将崩溃,您似乎需要在数据源(UICollectionViewDiffableDataSource)上运行快照,正如您所看到的,错误中指出:“当充当UICollectionView的数据源时,UICollectionViewDiffableDataSource API:请不要直接在UICollectionView上调用变异API”

试试这个:

// MARK: - Properties
  var dataSource: UICollectionViewDiffableDataSource<Int, UIImage>?

  var entry: Entry? {
    didSet {
      guard let entry = entry else { return }
      let dateFormatter = DateFormatter()
      dateFormatter.setLocalizedDateFormatFromTemplate("MMM dd yyyy, hh:mm")
      title = dateFormatter.string(from: entry.dateCreated)
    }
  }
而重新加载快照是:

private func reloadSnapshot(animated: Bool) {
    var snapshot = NSDiffableDataSourceSnapshot<Int, UIImage>()
    snapshot.appendSections([0])
    snapshot.appendItems(entry?.images ?? [])
    dataSource?.apply(snapshot, animatingDifferences: animated)
  }

如果您正在更改collectionView.PerformBatchUpdate()以更新您的collectionView,这将崩溃,似乎您需要在您的数据源(UICollectionViewDiffableDataSource)上使用快照正如您所看到的,错误中指出:“当充当UICollectionView的数据源时,UICollectionViewDiffableDataSource API:请不要直接在UICollectionView上调用变异API”

试试这个:

// MARK: - Properties
  var dataSource: UICollectionViewDiffableDataSource<Int, UIImage>?

  var entry: Entry? {
    didSet {
      guard let entry = entry else { return }
      let dateFormatter = DateFormatter()
      dateFormatter.setLocalizedDateFormatFromTemplate("MMM dd yyyy, hh:mm")
      title = dateFormatter.string(from: entry.dateCreated)
    }
  }
而重新加载快照是:

private func reloadSnapshot(animated: Bool) {
    var snapshot = NSDiffableDataSourceSnapshot<Int, UIImage>()
    snapshot.appendSections([0])
    snapshot.appendItems(entry?.images ?? [])
    dataSource?.apply(snapshot, animatingDifferences: animated)
  }
所有这些都是基于raywenderlich.com“Catalyst by Tutorials”的阅读和工作

如果您正在使用
UICollectionViewDropDelegate
方法返回
UICollectionViewDropProposal
collectionView(\uuuU9:dropSessionDidUpdate:WithDestinationIndepath DestinationIndepath:)
,此方法将在设置放置动画时调用引擎盖下的不可扩散方法

我的猜测是,苹果从未将其与分散数据源一起测试过。您需要删除此方法,并以不同的方式自行实现其动画。

还有一件事:

如果您正在使用
UICollectionViewDropDelegate
方法返回
UICollectionViewDropProposal
collectionView(\uuuU9:dropSessionDidUpdate:WithDestinationIndepath DestinationIndepath:)
,此方法将在设置放置动画时调用引擎盖下的不可扩散方法


我的猜测是,苹果从未将其与可扩散数据源一起测试过。你需要删除此方法,并以不同的方式自己实现其动画。

它们可能真的不兼容。在我看来,关于可扩散数据源的很多问题都没有经过很好的思考。你有没有设法解决过这个问题?我有点担心同一个now@matt我想他们只是忘记了拖放。合成版面也不太支持它。它们可能真的不兼容。在我看来,有很多关于分散数据源的问题没有经过深思熟虑。你有没有解决过这个问题?我也有同样的问题now@matt我想他们只是rgot关于拖放。合成版面也不太支持它。