Search code examples
iosswiftuicollectionviewuicollectionviewcelluicollectionviewlayout

Changing and persisting UICollectionView Cell Size on Tap


I'm working on a project which uses a custom FlowLayout which I cannot really touch but I do need to expand the sidth of the cell one a user has tapped on it so it takes the full width of the CollectionView boounds AND also persist that change throughout the viewController's lifecycle regardlee of how mych the CollectionView has been scrolled.

Right now I have managed to have the cell expand on tap but if you scroll enough through the collecion view, if you scroll back the cell is scaled back to it's original size. Now I think this is happening because of the cell reuse but I have no idea on how to model this behaviour.

Here's some more details:

I have a XIB for the Cell

in sizeForItem I have :

      guard let cellViewModel = viewModel?.getCellViewModel(forIndexPath: indexPath) else {
            return CGSize(width: collectionView.frame.width, height: 100)
        }

        var font = UIFont.systemFont(ofSize: 16, weight: .medium)
        var fontAttributes = [NSAttributedString.Key.font: font]
        let nameText = cellViewModel.variationName
        let nameSize = (nameText as NSString).size(withAttributes: fontAttributes)

        font = UIFont.systemFont(ofSize: 16)
        fontAttributes = [NSAttributedString.Key.font: font]
        let priceText = cellViewModel.priceString ?? ""
        let priceSize = (priceText as NSString).size(withAttributes: fontAttributes)

        let contentWidth = nameSize.width + priceSize.width + 38 + 1

        let insetsTotal = collectionView.contentInset.left
        + collectionView.contentInset.right
        + collectionView.safeAreaInsets.right
        + collectionView.safeAreaInsets.left

        var defaultWidth: CGFloat {
            if contentWidth > collectionView.frame.width {
                return collectionView.frame.width - insetsTotal
            } else {
                return contentWidth
            }
        }

      guard let isCellSelected = collectionView.cellForItem(at: indexPath)?.isSelected else { return CGSize(width: defaultWidth, height: 33) }

        var currentWidth = defaultWidth

        if isCellSelected {
            currentWidth = collectionView.frame.width - insetsTotal
        } else {
            currentWidth = defaultWidth
        }

        let size = CGSize(width: currentWidth, height: 33)

        return size
    }

And then I have didSelectItemAt and didDeselectItemAt perform batch updates on the collectionview


func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath){         collectionView.performBatchUpdates(nil, completion: nil)
 }

 func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath {        collectionView.performBatchUpdates(nil, completion: nil)
    }

The behaviour that I get is a nice expanding cell that actually looks animated but the width is not preserved on scroll. The collection-view has multiple sections so if you scroll down to a different section and time items there, then some expanded cells from the previous section would scale down. I think this is due to cell reuse bit I could really use some help!


Solution

  • You have line like this:

    let isCellSelected = collectionView.cellForItem(at: indexPath)?.isSelected
    

    but when cell is preparing to reuse then CollectionView update isSelected property with information stored in Collection view.

    The correct way to check if a cell with a given indexPath is selected:

    let isCellSelected = (collectionView.indexPathsForSelectedItems ?? []).contains(indexPath)