Unable to resize cells with custom UICollectionViewFlowLayout

I’m currently trying to implement custom divider views in between each cell of my collection view. I found this answer online that adds the custom view in the inter-line spacing (link).

private let separatorDecorationView = "separator"  final class CustomFlowLayout: UICollectionViewFlowLayout {     override init() {         super.init()         register(SeparatorView.self,                  forDecorationViewOfKind: separatorDecorationView)     }      required init?(coder: NSCoder) {         fatalError("init(coder:) has not been implemented")     }      override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {         let layoutAttributes = super.layoutAttributesForElements(in: rect) ?? []         let lineWidth = self.minimumLineSpacing          var decorationAttributes: [UICollectionViewLayoutAttributes] = []          // skip first cell         for layoutAttribute in layoutAttributes where layoutAttribute.indexPath.item > 0 {             let separatorAttribute = UICollectionViewLayoutAttributes(forDecorationViewOfKind: separatorDecorationView,                                                                       with: layoutAttribute.indexPath)             let cellFrame = layoutAttribute.frame             separatorAttribute.frame = CGRect(x: cellFrame.origin.x,                                               y: cellFrame.origin.y - lineWidth,                                               width: cellFrame.size.width,                                               height: lineWidth)             separatorAttribute.zIndex = Int.max             decorationAttributes.append(separatorAttribute)         }          return layoutAttributes + decorationAttributes     } }  private final class SeparatorView: UICollectionReusableView {     private let imageView: UIImageView = {         let iv = UIImageView(image: UIImage(named: "cell-divider"))         iv.contentMode = .scaleAspectFit         iv.translatesAutoresizingMaskIntoConstraints = false         return iv     }()      override init(frame: CGRect) {         super.init(frame: frame)         addSubview(imageView)          imageView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true         imageView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true         imageView.topAnchor.constraint(equalTo: topAnchor).isActive = true         imageView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true     }      required init?(coder aDecoder: NSCoder) {         fatalError("init(coder:) has not been implemented")     }      override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) {         self.frame = layoutAttributes.frame     } } 

This solution actually works, and I’m able to see the dividers. The problem arises, however, when the user clicks on one of the cells. The behavior I want is for the cell to expand to show more details when the cell is clicked. The way I’m implementing this is by keeping track of which indexPaths are selected, and returning a larger size if they are selected in sizeForItemAt. In didSelectItemAt, I reload the collection view. This approach works when I’m using the normal UICollectionViewFlowLayout, but when I try using my custom flow layout (above), I get the following crash:

no UICollectionViewLayoutAttributes instance for -layoutAttributesForDecorationViewOfKind: separator at path <NSIndexPath: 0xf75c5b66b8a0a8ab> {length = 2, path = 0 - 6} 

I tried looking up solutions and found these two stack overflows here and here but none of the answers I tried seemed to work.

I tried:

  1. Invalidating the layout when I reload the collection view.
  2. Implementing a cache that I return from when I override layoutAttributesForItem in my custom layout.

Any help would be greatly appreciated at this point!

Add Comment
1 Answer(s)

It seems that I had to overwrite two methods in my custom layout:

    override func layoutAttributesForDecorationView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {         let layoutAttributes = UICollectionViewLayoutAttributes(forDecorationViewOfKind: elementKind,                                                                 with: indexPath)         return layoutAttributes;     }      override func initialLayoutAttributesForAppearingDecorationElement(ofKind elementKind: String, at decorationIndexPath: IndexPath) -> UICollectionViewLayoutAttributes? {         let attributes = layoutAttributesForDecorationView(ofKind: elementKind,                                                            at: decorationIndexPath)         return attributes     } 

I also needed to keep the layout invalidation call when I reloaded the data.

collectionView.reloadData() let context = collectionViewLayout.invalidationContext(forBoundsChange: bounds) context.contentOffsetAdjustment = CGPoint.zero collectionView.collectionViewLayout.invalidateLayout(with: context)  layoutSubviews() 
Answered on August 31, 2020.
Add Comment

Your Answer

By posting your answer, you agree to the privacy policy and terms of service.