UICollectionViewDropDelegate 和 DiffableDataSource
Posted
技术标签:
【中文标题】UICollectionViewDropDelegate 和 DiffableDataSource【英文标题】:UICollectionViewDropDelegate and DiffableDataSource 【发布时间】:2020-04-29 03:59:31 【问题描述】:我正在尝试使用 DiffableDataSource(在 ios 13 中引入)实现 UICollectionViewDropDelegate
。
UICollectionViewDragDelegate
,效果很好。
我设置collectionView.dropDelegate = self
我实现了func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator)
(你甚至可以在方法为空的情况下尝试)。
应用程序将构建并运行,但一旦您开始拖动单元格,应用程序就会立即崩溃并显示以下消息:
由于未捕获的异常“NSInternalInconsistencyException”而终止应用,原因:“作为 UICollectionView 的数据源时,必须通过 UICollectionViewDiffableDataSource API 更新 UICollectionView:请不要直接在 UICollectionView 上调用突变 API。
所以看起来这个方法试图直接修改 UICollectionView(也许在移动项目时移动单元格?)我不知道如何绕过这种行为。
想法?
【问题讨论】:
它们可能真的不兼容。在我看来,关于 diffable 数据源的很多内容都没有经过深思熟虑。 你有没有设法解决这个问题?我现在也有同样的 @matt 我认为他们只是忘记了拖放。组合式布局也不能很好地支持它。 【参考方案1】:这是一个相当容易解决的问题。从 UICollectionViewDropDelegate 函数 performDropWith 开始。请注意,我只需要 .move 而不需要 .copy,但您只需添加一个类似于以下 reorderItems() 方法的 copyItems() 方法:
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
然后我们有了处理collectionView 中实际更改所需的函数reorderItems()。需要明确的一点是快照 (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)
【讨论】:
【参考方案2】:如果您使用 collectionView.performBatchUpdates() 进行更改以更新您的 collectionView,这将崩溃,您似乎需要在数据源 (UICollectionViewDiffableDataSource) 上处理快照,如您所见,错误中指出:“UICollectionViewDiffableDataSource APIs作为 UICollectionView 的 dataSource 时:请不要直接在 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)
你的条目是一个结构:
struct Entry
let id = UUID().uuidString
let dateCreated = Date()
var log: String?
var images: [UIImage] = []
var isFavorite: Bool = false
extension Entry: Hashable
func hash(into hasher: inout Hasher)
hasher.combine(dateCreated)
hasher.combine(log)
static func == (lhs: Entry, rhs: Entry) -> Bool
return lhs.dateCreated == rhs.dateCreated &&
lhs.log ?? "" == rhs.log ?? "" &&
lhs.images == rhs.images &&
lhs.isFavorite == rhs.isFavorite
而 reloadSnapshot 是:
private func reloadSnapshot(animated: Bool)
var snapshot = NSDiffableDataSourceSnapshot<Int, UIImage>()
snapshot.appendSections([0])
snapshot.appendItems(entry?.images ?? [])
dataSource?.apply(snapshot, animatingDifferences: animated)
终于在你的 UICollectionViewDropDelegate 上:
func collectionView( _ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator)
let destinationIndex = coordinator.destinationIndexPath?.item ?? 0
for item in coordinator.items
if coordinator.session.localDragSession != nil,
let sourceIndex = item.sourceIndexPath?.item
self.entry?.images.remove(at: sourceIndex)
item.dragItem.itemProvider.loadObject(ofClass: UIImage.self)
(object, error) in
guard let image = object as? UIImage, error == nil else
print(error ?? "Error: object is not UIImage")
return
DispatchQueue.main.async
self.entry?.images.insert(image, at: destinationIndex)
self.reloadSnapshot(animated: true)
我的所有这些都是基于阅读和工作 raywenderlich.com “Catalyst by Tutorials”
【讨论】:
【参考方案3】:还有一件事:
如果您使用UICollectionViewDropDelegate
方法返回UICollectionViewDropProposal
、collectionView(_:dropSessionDidUpdate:withDestinationIndexPath destinationIndexPath:)
,则此方法将在为drop 设置动画时在后台调用非diffable 方法。
我的猜测是,Apple 从未将其与可区分的数据源一起测试过。您需要删除此方法并以不同的方式自己实现其动画。
【讨论】:
以上是关于UICollectionViewDropDelegate 和 DiffableDataSource的主要内容,如果未能解决你的问题,请参考以下文章