移动时保持 UICollectionView 中自定义单元格的大小

Keep size of a custom cell in a UICollectionView when moving

我有一个水平的集合视图,每个单元格都有自己的宽度。我使用默认的 UICollectionViewFlowLayout。重新排列时,单元格继承了它在此移动过程中覆盖的单元格的宽度,这是我不想要的。知道如何避免这种行为吗?

因此,如果您的 UICollectionView 具有不同大小的单元格,并且您希望在使用标准 UICollectionViewFlowLayout 移动时保持它们的大小,请使用此:

extension UICollectionViewFlowLayout {

@available(iOS 9.0, *)
open override func invalidationContext(forInteractivelyMovingItems targetIndexPaths: [IndexPath], withTargetPosition targetPosition: CGPoint, previousIndexPaths: [IndexPath], previousPosition: CGPoint) -> UICollectionViewLayoutInvalidationContext {

    let context = super.invalidationContext(forInteractivelyMovingItems: targetIndexPaths, withTargetPosition: targetPosition, previousIndexPaths: previousIndexPaths, previousPosition: previousPosition)

    //Check that the movement has actually happeneds
    if previousIndexPaths.first!.item != targetIndexPaths.first!.item {
        collectionView?.dataSource?.collectionView?(collectionView!, moveItemAt: previousIndexPaths.first!, to: targetIndexPaths.last!)
    }

    return context
}

open override func invalidationContextForEndingInteractiveMovementOfItems(toFinalIndexPaths indexPaths: [IndexPath], previousIndexPaths: [IndexPath], movementCancelled: Bool) -> UICollectionViewLayoutInvalidationContext {
    return super.invalidationContextForEndingInteractiveMovementOfItems(toFinalIndexPaths: indexPaths, previousIndexPaths: previousIndexPaths, movementCancelled: movementCancelled)
}

@available(iOS 9.0, *) //If you'd like to apply some formatting as the dimming of the movable cell
open override func layoutAttributesForInteractivelyMovingItem(at indexPath: IndexPath, withTargetPosition position: CGPoint) -> UICollectionViewLayoutAttributes {
    let attributes = super.layoutAttributesForInteractivelyMovingItem(at: indexPath, withTargetPosition: position)
    attributes.alpha = 0.8
    return attributes
}

}

在控制器的 moveItemAt 方法中,不要忘记检查压力状态是否已更改,例如:

override func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath,
                             to destinationIndexPath: IndexPath) {

    if #available(iOS 9.0, *) {

        if self.longPressureGestureRecognizer.state == .ended {
            return
        }

        //Update your data
        data.moveItem(at: sourceIndexPath.row, to: destinationIndexPath.row)

    }
}

我通过使用 collectionView:targetIndexPathForMoveFromItemAtIndexPath:toProposedIndexPath: 解决了这个问题,如果我的 row/section 数据的副本不存在,我会创建它,然后更新副本,就好像提议的移动是实际的一样移动。如果副本存在,我的 collectionView:layout:sizeForItemAtIndexPath: 会使用它。我会在 collectionView:moveItemAtIndexPath:toIndexPath: 中将副本设置为 nil,以便视图返回使用 "original" row/section 数据来调整大小。结果,所有尺寸在移动之前、期间和之后看起来都是正确的。

根据要求,这是我的代码:

- (NSIndexPath *)collectionView:(UICollectionView *)collectionView targetIndexPathForMoveFromItemAtIndexPath:(NSIndexPath *)originalIndexPath toProposedIndexPath:(NSIndexPath *)proposedIndexPath
{
    if (nil == _movingSections)
        _movingSections = [_sections copy];
    NSMutableArray *section = [_movingSections.sections objectAtIndex:originalIndexPath.section];
    id obj = [section objectAtIndex:originalIndexPath.row];

    [section removeObjectAtIndex:originalIndexPath.row];

    section = [_movingSections.sections objectAtIndex:proposedIndexPath.section];
    if (proposedIndexPath.row < [section count])
        [section insertObject:obj atIndex:proposedIndexPath.row];
    else
        [section addObject:obj];

    return proposedIndexPath;
}

我还更新了 collectionView:layout:sizeForItemAtIndexPath: 以在 _movingSections 非零时使用 _movingSections 而不是 _sections。在 collectionView:moveItemAtIndexPath:toIndexPath: 中,我将 _movingSections 设置为 nil。就是这样!

我应该补充一点,我最终放弃了这个方法,因为我有一些部分可以比设备宽度宽,在你移动某些东西之前它工作正常,此时 UICollectionViewFlowLayout 想要均匀地 space单元格,无论您提供的宽度如何。相反,我转而使用 UIScrollView,现在我的代码更简单了,而且我不再与 UICollectionViewFlowLayout 作斗争。我考虑过滚动我自己的 UICollectionViewLayout,但这并没有使我的代码更简单。