诀窍在于实现
override func collectionView(_ collectionView: UICollectionView,
targetIndexPathForMoveFromItemAt orig: IndexPath,
toProposedIndexPath prop: IndexPath) -> IndexPath {
在拖动过程中,该方法会被重复调用,但会出现一个单元格与另一个单元格交叉的时刻,并且单元格会被推开以进行补偿。此时,orig 和 prop 具有不同的值。因此,此时您需要根据单元格的移动方式修改所有尺寸。
为此,您需要在重新排列尺寸时模拟界面在单元格移动时所做的事情。运行时对此没有任何帮助!
这是一个简单的例子。假设用户只能在同一部分内移动单元格。并假设我们的数据模型看起来像这样,一旦collectionView(_:layout:sizeForItemAt:) 最初计算了它,每个 Item 都会记住它自己的大小:
struct Item {
var size : CGSize
// other stuff
}
struct Section {
var itemData : [Item]
// other stuff
}
var sections : [Section]!
sizeForItemAt: 将计算出的尺寸记忆到模型中的方式如下:
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
let memosize = self.sections[indexPath.section].itemData[indexPath.row].size
if memosize != .zero {
return memosize
}
// no memoized size; calculate it now
// ... not shown ...
self.sections[indexPath.section].itemData[indexPath.row].size = sz // memoize
return sz
}
然后,当我们听说用户以一种使单元格移动的方式拖动时,我们读取了该部分的所有 size 值,执行与界面相同的删除和插入操作,然后放入重新排列的 size 值回到模型中:
override func collectionView(_ collectionView: UICollectionView,
targetIndexPathForMoveFromItemAt orig: IndexPath, toProposedIndexPath
prop: IndexPath) -> IndexPath {
if orig.section != prop.section {
return orig
}
if orig.item == prop.item {
return prop
}
// they are different, we're crossing a boundary - shift size values!
var sizes = self.sections[orig.section].rowData.map{$0.size}
let size = sizes.remove(at: orig.item)
sizes.insert(size, at:prop.item)
for (ix,size) in sizes.enumerated() {
self.sections[orig.section].rowData[ix].size = size
}
return prop
}
结果是collectionView(_:layout:sizeForItemAt:) 现在在拖动过程中给出了正确的结果。
额外的难题是,当拖动开始时,您需要保存所有原始尺寸,而当拖动结束时,您需要将它们全部恢复, 这样当拖动 结束 时结果也是正确的。