mat*_*att 1 ios uicollectionview
假设我有一个带有UICollectionViewFlowLayout的UICollectionView,并且我的项目大小不同。所以我已经实施了collectionView(_:layout:sizeForItemAt:)。
现在假设我允许用户重新排列项目(collectionView(_:canMoveItemAt:))。
这是问题所在。当一个单元格被拖动而其他单元格移开时,collectionView(_:layout:sizeForItemAt:)被重复调用。但这显然是需要使用错误的索引路径的:单元格的大小与它在视觉上移动到的位置的索引路径相同。因此,它在拖曳到另一个位置时在拖动过程中采用了错误的大小。
一旦拖动结束并被collectionView(_:moveItemAt:to:)调用,并且我更新了数据模型并重新加载了数据,所有单元格都将假定其正确大小。仅在拖动过程中会出现问题。
显然,我们没有得到足够的信息collectionView(_:layout:sizeForItemAt:)来知道在拖曳继续进行时要返回什么答案。也许我应该说,我们被要求提供错误索引路径的大小。
我的问题是:人们到底在做什么?
诀窍是实施
override func collectionView(_ collectionView: UICollectionView,
targetIndexPathForMoveFromItemAt orig: IndexPath,
toProposedIndexPath prop: IndexPath) -> IndexPath {
Run Code Online (Sandbox Code Playgroud)
在拖动过程中,该方法被反复调用,但是有一瞬间,一个细胞越过另一个细胞,而细胞被推开以进行补偿。那一刻,orig又有prop不同的价值观。因此,此时您需要根据单元的移动方式来修改所有大小。
为此,您需要模拟尺寸的重新排列,以使单元在移动时界面正在执行的操作。运行时对此没有帮助!
这是一个简单的例子。假定用户只能在同一部分内移动单元格。并假定我们的数据模型如下所示,每个项目collectionView(_:layout:sizeForItemAt:)在最初计算完它后便会记住自己的大小:
struct Item {
var size : CGSize
// other stuff
}
struct Section {
var itemData : [Item]
// other stuff
}
var sections : [Section]!
Run Code Online (Sandbox Code Playgroud)
以下是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
}
Run Code Online (Sandbox Code Playgroud)
然后,当我们听到用户拖动鼠标以使其移动单元格的方式拖动时,我们读入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
}
Run Code Online (Sandbox Code Playgroud)
结果是collectionView(_:layout:sizeForItemAt:)现在在拖动过程中给出了正确的结果。
另一个难题是,当拖动开始时,您需要保存所有原始大小,当拖动结束时,您需要恢复所有原始大小,以便在拖动结束时,结果也将是正确的。
虽然接受的答案非常聪明(支持你马特),但它实际上是一个不必要的复杂黑客。有一个更简单的解决方案。
关键是:
这就是它的样子......
// MARK: UICollectionViewDataSource
override func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
// (This method will be empty!)
// As the Docs states: "You must implement this method to support
// the reordering of items within the collection view."
// However, its implementation should be empty because, as explained
// in (2) from above, we do not want to manipulate our data when the
// cell finishes moving, but at the exact moment it enters a new
// indexPath.
}
override func collectionView(_ collectionView: UICollectionView, targetIndexPathForMoveFromItemAt originalIndexPath: IndexPath, toProposedIndexPath proposedIndexPath: IndexPath) -> IndexPath {
// This will be true at the exact moment the "moving" cell enters
// a new indexPath.
if originalIndexPath != proposedIndexPath {
// Here, we rearrange our data to reflect the new position of
// our cells.
let removed = myDataArray.remove(at: originalIndexPath.item)
myDataArray.insert(removed, at: proposedIndexPath.item)
}
return proposedIndexPath
}
// MARK: UICollectionViewDelegateFlowLayout
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
// Finally, we simply fetch cell sizes from the properly arranged
// data.
let myObject = myDataArray[indexPath.item]
return myObject.size
}
Run Code Online (Sandbox Code Playgroud)