-->

Keep size of a custom cell in a UICollectionView w

2019-03-30 11:11发布

问题:

I have a horizontal collection view and every cell has it's own width. I use the default UICollectionViewFlowLayout. When rearranging, cell inherits the width of the cell which it overlays during this movement, what I don't want. Any idea, how to avoid this behavior?

回答1:

So, If you have the UICollectionView with cells of different sizes and you'd like to keep their sizes when moving using the standard UICollectionViewFlowLayout use this:

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
}

}

In the moveItemAt method of your controller don't forget to check that the pressure state is changed like:

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)

    }
}


回答2:

I solved this by using collectionView:targetIndexPathForMoveFromItemAtIndexPath:toProposedIndexPath: where if a copy of my row/section data didn't exist, I'd create it, then update the copy as if the proposed move was an actual move. My collectionView:layout:sizeForItemAtIndexPath: would use the copy if it existed. I would set the copy to nil in collectionView:moveItemAtIndexPath:toIndexPath: so that the view would go back to using the "original" row/section data for sizing. All sizes look correct before, during, and after moving as a result.

As requested, here's my code:

- (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;
}

I also updated collectionView:layout:sizeForItemAtIndexPath: to use _movingSections instead of _sections when _movingSections is non-nil. And in collectionView:moveItemAtIndexPath:toIndexPath: I set _movingSections to nil. And that's it!

I should add that I ended up abandoning this method since I have sections that can be wider than the device width, which works fine until you move something, and at that point UICollectionViewFlowLayout wants to evenly space the cells regardless of the widths you've provided. Instead, I moved to using a UIScrollView and now my code is much simpler, plus I'm no longer fighting with UICollectionViewFlowLayout. I considered rolling my own UICollectionViewLayout, but that did not make my code simpler.