6

As described in the Collection View Programming Guide for iOS, if you want to add new supplementary or decoration views to your Flow Layout, you have to subclass UICollectionViewFlowLayout.

I need help to implement the following steps in Swift 3:

The standard flow layout class supports only section header and section footer views and no decoration views. To support additional supplementary and decoration views, you need to override the following methods at a minimum:

In your layoutAttributesForElementsInRect: method, you can call super to get the layout attributes for the cells and then add the attributes for any new supplementary or decoration views that are in the specified rectangle. Use the other methods to provide attributes on demand.

Goals I would like to achieve:

  • One supplementary view above each item
  • Delegate method like referenceSizeForHeaderInSection to define the size of the new supplementary view inside the collection view controller (return CGSize.zero if there shouldn't be the "item header")
  • Accurate calculated content size
  • Insert, update and delete animation of the supplementary view with each item

Update

I know this question is too broadly defined. But with the help of Devon and some own ideas I found a solution which works for me.

  1. Subclass UICollectionViewFlowLayout

    class SBCollectionViewFlowLayout: UICollectionViewFlowLayout { … }

  2. Override layoutAttributesForElements(in rect: CGRect) to add the additional supplementary view to each cell.

    // Check if there already valid layout attributes
    guard var layoutAttributes = super.layoutAttributesForElements(in: rect) else { return nil }
    
    // Try to append new supplementary view to each cell
    // ────────────────────────────────────────────────────────────      
    
    for attributes in layoutAttributes where attributes.representedElementCategory == .cell {
        if let supplementaryAttributes = layoutAttributesForSupplementaryView(ofKind: UICollectionElementKindDateSeparator, at: attributes.indexPath) {
            layoutAttributes.append(supplementaryAttributes)
        }
    }
    
    return layoutAttributes
    
  3. Override layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) to calculate the position of the new supplementary view inside the collection view.

    switch elementKind {
    
    case UICollectionElementKindDateSeparator:
    
        // Return if there is no collection view or delegate
        guard let delegate = self.collectionView?.delegate as? SBCollectionViewDelegateFlowLayout else { return nil }
    
        // Get the dynamic size of from the delegate
        let size = delegate.collectionView!(self.collectionView!, layout: self, referenceSizeForSeparatorAt: indexPath)
    
        // Return if there the size is (0, 0)
        if size == CGSize.zero { return nil }
    
        // Create new layout attributes for the separator
        // ════════════════════════════════════════════════════════════
    
        let attributes = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: elementKind, with: indexPath)
    
        if let itemAttributes = layoutAttributesForItem(at: indexPath) {
    
            // Position supplementary view at top left edge of the cell
            // ────────────────────────────────────────────────────────────
    
            attributes.frame = CGRect(x: itemAttributes.frame.origin.x,
                                      y: itemAttributes.frame.origin.y,
                                      width:  size.width,
                                      height: size.height)
    
            // Increase the item’s position on the z axis
            // to be sure that the item appear on top of the cell.
            attributes.zIndex = 1
        }
    
        return attributes
    
    default:
        // If it is not a date separator, return attributes from flow layout
        return super.layoutAttributesForSupplementaryView(ofKind: elementKind, at: indexPath)
    }
    
  4. "Extend" the UICollectionViewDelegateFlowLayout to ask the delegate about the size of the new header.

    public protocol SBCollectionViewDelegateFlowLayout: UICollectionViewDelegateFlowLayout  {
        func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForSeparatorAt indexPath: IndexPath) -> CGSize
    }
    

TL;DR

The idea is instead of inserting the additional supplementary views to the collection view and then to recalculate all items, I add the supplementary view on top of the corresponding cell. It is important to add a dynamic top padding to the cell that the supplementary view is not overlaying any content inside the cell.

I hope this helps others with a similar problem!

0 Answers0