Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iOS 10/11 UICollectionViewFlowLayout using UICollectionViewFlowLayoutAutomaticSize results in footer supplementary view misaligned

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

like image 334
dlbuckley Avatar asked Oct 19 '16 10:10

dlbuckley


2 Answers

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.

like image 183
Anton Lagutin Avatar answered Nov 07 '22 14:11

Anton Lagutin


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.

like image 1
Ashutosh Pandey Avatar answered Nov 07 '22 12:11

Ashutosh Pandey