23

So this is an interesting issue we found with UICollectionViewFlowLayout on iOS 10 (still an issue on 11) and using UICollectionViewFlowLayoutAutomaticSize for the estimatedItemSize.

We've found that using UICollectionViewFlowLayoutAutomaticSize for the estimatedItemSize results in the footer supplementary view floating above the bottom few cells.(Red/Pink is the header, Green is the footer.)

UICollectionViewFlowLayoutAutomaticSize problem enter image description here

Here is the VC code of a sample app:

import UIKit

class ViewController: UIViewController {

    // MARK: Properties

    let texts: [String] = [
        "This is some text",
        "This is some more text",
        "This is even more text"
    ]

    // MARK: Outlets

    @IBOutlet var collectionView: UICollectionView! {
        didSet {
            self.collectionView.backgroundColor = .orange
        }
    }

    // MARK: Lifecycle

    override func viewDidLoad() {

        super.viewDidLoad()

        // Layout
        let layout = UICollectionViewFlowLayout()
        layout.scrollDirection = .vertical
        if #available(iOS 10.0, *) {
            layout.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize
        } else {
            layout.estimatedItemSize = CGSize(width: self.collectionView.bounds.width, height: 50)
        }
        self.collectionView.collectionViewLayout = layout

        // Register Cells
        self.collectionView.register(UINib(nibName: "TextCell", bundle: nil), forCellWithReuseIdentifier: String(describing: TextCell.self))
        self.collectionView.register(UINib(nibName: "SectionHeader", bundle: nil), forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: String(describing: SectionHeader.self))
        self.collectionView.register(UINib(nibName: "SectionFooter", bundle: nil), forSupplementaryViewOfKind: UICollectionElementKindSectionFooter, withReuseIdentifier: String(describing: SectionFooter.self))

        self.collectionView.reloadData()
    }
}

// MARK: - UICollectionViewDelegateFlowLayout Methods

extension ViewController: UICollectionViewDelegateFlowLayout {

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {

        return CGSize(width: self.collectionView.bounds.width, height: 90)
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {

        return CGSize(width: self.collectionView.bounds.width, height: 90)
    }
}

// MARK: - UICollectionViewDataSource Methods

extension ViewController: UICollectionViewDataSource {

    public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {

        return self.texts.count
    }

    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return 1
    }

    public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: TextCell.self), for: indexPath)

        if let textCell = cell as? TextCell {

            let text = self.texts[indexPath.row]
            textCell.configure(text: text)
        }

        return cell
    }

    func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {

        switch kind {
        case UICollectionElementKindSectionHeader:

            return collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: String(describing: SectionHeader.self), for: indexPath)

        case UICollectionElementKindSectionFooter:

            return collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: String(describing: SectionFooter.self), for: indexPath)

        default:
            return UICollectionReusableView()
        }
    }
}

// MARK: - UICollectionViewDelegate Methods

extension ViewController: UICollectionViewDelegate {

}

Has anyone managed to get UICollectionViewFlowLayoutAutomaticSize to work on iOS 10 with supplementary header and footer views? If I add a size to the estimatedItemSize then it appears to work, but I want to know if there is a bug in using the new iOS 10 feature or if I'm using it incorrectly.

The bug filed with Apple has the ID: 28843116

UPDATE: This still appears to be an issue on 10.3 Beta 1

UPDATE 2: This still appears to be an issue in iOS 11 Beta 1

dlbuckley
  • 685
  • 1
  • 5
  • 14

2 Answers2

24

UICollectionViewFlowLayout supports auto layout for cells very well, BUT it does not supports it for supplementary views. Each time when auto layout code updates cell's frame it does nothing with headers and footers. So you need to tell layout that it should invalidate headers and footers. And there is a method for this!

You should override UICollectionViewLayout's func invalidationContext(forPreferredLayoutAttributes: UICollectionViewLayoutAttributes, withOriginalAttributes: UICollectionViewLayoutAttributes) and perform supplementary view invalidation in it.

Example:

override open func invalidationContext(forPreferredLayoutAttributes preferred: UICollectionViewLayoutAttributes,
        withOriginalAttributes original: UICollectionViewLayoutAttributes)
                -> UICollectionViewLayoutInvalidationContext {
    let context: UICollectionViewLayoutInvalidationContext = super.invalidationContext(
            forPreferredLayoutAttributes: preferred,
            withOriginalAttributes: original
    )

    let indexPath = preferred.indexPath

    if indexPath.item == 0 {
        context.invalidateSupplementaryElements(ofKind: UICollectionElementKindSectionHeader, at: [indexPath])
    }

    return context
}

In example above I am using invalidation context provided by UICollectionViewFlowLayout to invalidate header supplementary view if first cell in section was invalidated. You can use this method for footer invalidation as well.

Anton Lagutin
  • 241
  • 2
  • 5
  • 2
    This has solved the problem for me. Suddenly my header views were correctly positioned. – MurderDev Oct 05 '17 at 06:02
  • 1
    In my cause, I need to invalidate the cell itself as well inside the `if` scope by calling `context.invalidateItems(at:)`. – billibala Dec 01 '17 at 07:39
  • 1
    looks like in iOS12 there is no need to invalidate supplementary elements as they are been layouted ok – protspace Nov 15 '18 at 15:21
1

I came across the same problem. UICollectionViewFlowLayoutAutomaticSize doesn't work for supplementary views. Use the UICollectionViewDelegateFlowLayout to give the size explicitly. Its better to calculate the sizes and use delegates as automatic size calculations causes lagging, at times.

use referenceSizeForHeaderInSection and referenceSizeForFooterInSection delegate methods to give the header and footer size explicitly.

Ashutosh Pandey
  • 238
  • 2
  • 11
  • 1
    Thanks for the input. Although if you look at the code I posted I am using the `referenceSizeForHeaderInSection` and `referenceSizeForFooterInSection` delegate methods to set the height to 90pts. Or are you referring to something else? – dlbuckley Jan 27 '17 at 09:46
  • The code is fine. Remove this part and try giving the size pf the cells using calculations. `if #available(iOS 10.0, *) { layout.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize } else { layout.estimatedItemSize = CGSize(width: self.collectionView.bounds.width, height: 50) }` – Ashutosh Pandey Jan 27 '17 at 10:09
  • It works if I don't use `UICollectionViewFlowLayoutAutomaticSize`, but the whole point of the question was to understand how `UICollectionViewFlowLayoutAutomaticSize` worked. Our solution moved the automatic sizing in the end just like your suggestion, but it would still be nice to use automatic sizing in the future. – dlbuckley Jan 27 '17 at 10:47
  • You should use it with collection views that don't have supplementary views. I don't have any documentation but as i have experienced it returns the correct sizes for the cells only. It doesn't calculate the size and offset for supplementary views. – Ashutosh Pandey Jan 27 '17 at 11:28
  • See thats the strange thing, when you debug everything and look at the offsets that are produced they actually look correct. It's only when the views are positioned that oddities happen. I've looked through the documentation trying to hunt down the issue quite a few times now and it doesn't say anywhere that you shouldn't use `UICollectionViewFlowLayoutAutomaticSize` with supplementary views. Because of this I believe it's a bug in UIKit. It's much less of a 'shouldn't' use it, more like a 'can't' use it ;) – dlbuckley Jan 27 '17 at 11:47