I would like to create a custom UICollectionView able to react in a different way based on its configuration.
I created a custom subclass of UICollectionViewFlowLayout
and I set its estimatedItemSize
to UICollectionViewFlowLayout.automaticSize
. This component is also able to compute different layout width based on its configuration.
ColumnFlowLayout.swift
import UIKit
class ColumnFlowLayout: UICollectionViewFlowLayout {
let columns: Int
init(columns: Int, insets: UIEdgeInsets) {
self.columns = columns
super.init()
self.sectionInsetReference = .fromContentInset
self.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
self.minimumInteritemSpacing = insets.top
self.minimumLineSpacing = insets.bottom
self.sectionInset = insets
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let layoutAttributesObjects = super.layoutAttributesForElements(in: rect)?.map{ $0.copy() } as? [UICollectionViewLayoutAttributes]
layoutAttributesObjects?.forEach({ layoutAttributes in
if layoutAttributes.representedElementCategory == .cell {
if let newFrame = layoutAttributesForItem(at: layoutAttributes.indexPath)?.frame {
layoutAttributes.frame = newFrame
}
}
})
return layoutAttributesObjects
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
guard let layoutAttributes = super.layoutAttributesForItem(at: indexPath)?.copy() as? UICollectionViewLayoutAttributes else {
return nil
}
layoutAttributes.frame.size.width = self.estimatedWidth()
return layoutAttributes
}
private func estimatedWidth() -> CGFloat {
guard let collectionView = collectionView else { return 0.0 }
let marginsAndInsets = sectionInset.left + sectionInset.right + collectionView.safeAreaInsets.left + collectionView.safeAreaInsets.right + minimumInteritemSpacing * CGFloat(columns - 1)
return ((collectionView.bounds.size.width - marginsAndInsets) / CGFloat(columns)).rounded(.down)
}
}
Then I used it for UICollectionView configuration.
CollectionViewController.swift
private func prepareCollectionView() {
let columnLayout = ColumnFlowLayout(columns: Layout.COLUMNS, insets: Layout.INSETS)
self.collectionView!.collectionViewLayout = columnLayout
self.collectionView!.contentInsetAdjustmentBehavior = .always
...
}
In order to make it work, each UICollectionViewCell
has preferredLayoutAttributesFitting
BaseCollectionViewCell.swift
import UIKit
class BaseCollectionCell: UICollectionViewCell {
...
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
let targetSize = CGSize(width: layoutAttributes.frame.width, height: 0)
layoutAttributes.frame.size = contentView.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)
return layoutAttributes
}
...
}
The output is near to the desired result, except for the top spacing:
What I'd like to expect is to align each cell on top like that:
Do you have any suggestions?