使用UICollectionViewFlowLayout重新排列不同大小的UICollectionView项

mat*_*att 1 ios uicollectionview

假设我有一个带有UICollectionViewFlowLayout的UICollectionView,并且我的项目大小不同。所以我已经实施了collectionView(_:layout:sizeForItemAt:)

现在假设我允许用户重新排列项目(collectionView(_:canMoveItemAt:))。

这是问题所在。当一个单元格被拖动而其他单元格移开时,collectionView(_:layout:sizeForItemAt:)被重复调用。但这显然是需要使用错误的索引路径的:单元格的大小与它在视觉上移动到的位置的索引路径相同。因此,它在拖曳到另一个位置时在拖动过程中采用了错误的大小。

在此处输入图片说明

一旦拖动结束并被collectionView(_:moveItemAt:to:)调用,并且我更新了数据模型并重新加载了数据,所有单元格都将假定其正确大小。仅拖动过程中会出现问题。

显然,我们没有得到足够的信息collectionView(_:layout:sizeForItemAt:)来知道在拖曳继续进行时要返回什么答案。也许我应该说,我们被要求提供错误索引路径的大小。

我的问题是:人们到底在做什么?

mat*_*att 6

诀窍是实施

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:)现在在拖动过程中给出了正确的结果。

另一个难题是,当拖动开始时,您需要保存所有原始大小,当拖动结束时,您需要恢复所有原始大小,以便在拖动结束时,结果也将是正确的。


Eri*_*187 6

虽然接受的答案非常聪明(支持你马特),但它实际上是一个不必要的复杂黑客。有一个更简单的解决方案。

关键是:

  1. 将单元格大小存储在数据本身中。
  2. 当“移动”单元格进入新的索引路径时(而不是当单元格完成移动时)操作或“重新排列”数据。
  3. 直接从数据中获取单元格大小(现已正确排列)。

这就是它的样子......

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