William Robinson
William Robinson

Reputation: 1048

UICollectionViewCells are resetting their size after being rearranged

I've got a UICollectionView of cells with labels on them, and I would like to be able to reorder them by dragging and dropping.

The cells are sized with estimatedItemSize because I want them to be all different sizes, depending on the size of the text in the cell. I can get that to appear fine. However once I use collectionView.moveItemAtIndexPath the cells seem to resort to a default size.

Resetting the collectionView's flow layout straight after the move doesn't fix it either.

In viewDidLoad

    let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
    layout.estimatedItemSize = CGSize(width: 100, height: 100)
    collectionView.collectionViewLayout = layout

The UIPanGestureRecognizer that reorders the text

func handlePan(sender: UIPanGestureRecognizer) {
    let locationPoint = sender.locationInView(collectionView)
    if sender.state == UIGestureRecognizerState.Began {

        if let indexPath = collectionView.indexPathForItemAtPoint(locationPoint)? {

            if let cell = collectionView.cellForItemAtIndexPath(indexPath) {
                UIGraphicsBeginImageContext(cell.bounds.size)
                cell.layer.renderInContext(UIGraphicsGetCurrentContext())
                let cellImage = UIGraphicsGetImageFromCurrentImageContext()
                UIGraphicsEndImageContext()

                movingCellImage = UIImageView(image: cellImage)
                movingCellImage.center = locationPoint
                movingCellImage.transform = CGAffineTransformMakeScale(2, 2)
                collectionView.addSubview(movingCellImage)
                cell.alpha = 0
                movingCellOriginalIndexPath = indexPath

            }

        }

    } else if sender.state == UIGestureRecognizerState.Changed {
        movingCellImage.center = locationPoint

    } else if sender.state == UIGestureRecognizerState.Ended {

        if let indexPath = collectionView.indexPathForItemAtPoint(locationPoint)? {

            let cell = collectionView.cellForItemAtIndexPath(movingCellOriginalIndexPath)
            collectionView.moveItemAtIndexPath(movingCellOriginalIndexPath, toIndexPath: indexPath)
            cell?.alpha = 1

        }

        movingCellImage.removeFromSuperview()
    }
}

Before and after moving a UICollectionViewCell, having moved the second cell to the first

Upvotes: 2

Views: 1800

Answers (1)

bluedome
bluedome

Reputation: 2459

Reloading with animation seems to solve the issue.
But previous layout remains for a moment... You may need to create a custom UICollectionViewLayout instead of using estimatedSize.

if let indexPath = collectionView.indexPathForItemAtPoint(locationPoint)? {
    let cell = collectionView.cellForItemAtIndexPath(movingCellOriginalIndexPath)
    collectionView.moveItemAtIndexPath(movingCellOriginalIndexPath, toIndexPath: indexPath)
    cell?.alpha = 1

    collectionView.reloadSections(NSIndexSet(index: 0)) // not reloadData
}

[UPDATE]

As you point out, the key is -collectionView:layout:sizeForItemAtIndexPath:.

  • don't set estimatedSize to UICollectionViewFlowLayout
  • calculate each cell size in the delegate method above

like this:

func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
    let str = data[indexPath.item] // string to show in label

    // calculate size. you can use -boundingRectWithSize: as well
    var txtsize = (str as NSString).sizeWithAttributes([ NSFontAttributeName : UIFont.systemFontOfSize(16)]) // specify label's font
    txtsize.width += 10 // add margin if necessary
    txtsize.height = 100 // calculated text height or any value you want

    return txtsize
}

Upvotes: 1

Related Questions