This question already has an answer here:
-
How do you determine spacing between cells in UICollectionView flowLayout
11 answers
So, I'm trying to implement a tag list with UICollectionView. I'm following this tutorial: http://www.cocoanetics.com/2013/08/variable-sized-items-in-uicollectionview/
The issue is flow layout in UICollectionView tries to space items on the same row evenly.
As a developer, I can only specify minimumInteritemSpacingForSectionAtIndex, it's really up to the UICollectionView to determine the actual item spacing.
But what I really want to achieve is like this:
Any ideas?
I've converted Milo's solution to Swift: https://github.com/Coeur/UICollectionViewLeftAlignedLayout/
It simply subclasses UICollectionViewFlowLayout
.
import UIKit
/**
* Simple UICollectionViewFlowLayout that aligns the cells to the left rather than justify them
*
* Based on https://stackoverflow.com/questions/13017257/how-do-you-determine-spacing-between-cells-in-uicollectionview-flowlayout
*/
open class UICollectionViewLeftAlignedLayout: UICollectionViewFlowLayout {
open override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
return super.layoutAttributesForElements(in: rect)?.map { $0.representedElementKind == nil ? layoutAttributesForItem(at: $0.indexPath)! : $0 }
}
open override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
guard let currentItemAttributes = super.layoutAttributesForItem(at: indexPath)?.copy() as? UICollectionViewLayoutAttributes,
let collectionView = self.collectionView else {
// should never happen
return nil
}
let sectionInset = evaluatedSectionInsetForSection(at: indexPath.section)
guard indexPath.item != 0 else {
currentItemAttributes.leftAlignFrame(withSectionInset: sectionInset)
return currentItemAttributes
}
guard let previousFrame = layoutAttributesForItem(at: IndexPath(item: indexPath.item - 1, section: indexPath.section))?.frame else {
// should never happen
return nil
}
// if the current frame, once left aligned to the left and stretched to the full collection view
// width intersects the previous frame then they are on the same line
guard previousFrame.intersects(CGRect(x: sectionInset.left, y: currentItemAttributes.frame.origin.y, width: collectionView.frame.width - sectionInset.left - sectionInset.right, height: currentItemAttributes.frame.size.height)) else {
// make sure the first item on a line is left aligned
currentItemAttributes.leftAlignFrame(withSectionInset: sectionInset)
return currentItemAttributes
}
currentItemAttributes.frame.origin.x = previousFrame.origin.x + previousFrame.size.width + evaluatedMinimumInteritemSpacingForSection(at: indexPath.section)
return currentItemAttributes
}
func evaluatedMinimumInteritemSpacingForSection(at section: NSInteger) -> CGFloat {
return (collectionView?.delegate as? UICollectionViewDelegateFlowLayout)?.collectionView?(collectionView!, layout: self, minimumInteritemSpacingForSectionAt: section) ?? minimumInteritemSpacing
}
func evaluatedSectionInsetForSection(at index: NSInteger) -> UIEdgeInsets {
return (collectionView?.delegate as? UICollectionViewDelegateFlowLayout)?.collectionView?(collectionView!, layout: self, insetForSectionAt: index) ?? sectionInset
}
}
extension UICollectionViewLayoutAttributes {
func leftAlignFrame(withSectionInset sectionInset: UIEdgeInsets) {
frame.origin.x = sectionInset.left
}
}
Apple has provided the UICollectionViewFlowLayout class for us developers, which should be enough to solve the 'typical use case' of collection views. However, I believe you're correct in your assessment that the default layout does not allow you to create this tag cloud effect. If you need something different from the normal flow layout, you'll have to write your own subclass of UICollectionViewLayout.
Apple covers this topic in their 2012 WWDC session titled, "Advanced Collection Views and Building Custom Layouts"
Some additional Apple docs: https://developer.apple.com/library/ios/documentation/WindowsViews/Conceptual/CollectionViewPGforIOS/CreatingCustomLayouts/CreatingCustomLayouts.html
At the risk of seeming biased, I also wrote a quick blog post running through the basic steps: http://bradbambara.wordpress.com/2014/05/24/getting-started-with-custom-uicollectionview-layouts/
Hope that helps.