Self Sizing UICollectionViewCell in Swift
The iPhone and iOS has been around for more than a decade. UIKit has been around for that long as well. The iPhone started as one form factor one version of the iOS. Screen sizes have exploded over the preceding 10 years.
UITableView
was the original way to lay out data in a list or tabular form. UITableViewCells
were generally fixed and size and mostly static in content.
UICollectionView
was introduced in iOS 6. It is a newer, cleaner API that can adapt to multiple screen sizes on the iPhone and have a grid layout on iPad. UICollectionView
is more featureful than UITableView
and as a result can be more confusing to configure.
UICollectionViewDataSource
may look familiar, but UICollectionViewFlowLayoutDelegate
and UICollectionViewLayoutAttributes
can be confusing. Extra configuration is required if you want to have a UICollectionViewCell
take the full width of the screen on any iPhone screen just like a UITableViewCell
.
Creating a UICollectionViewController
You can follow along with example code in the SelfSizingUICollectionViewCell repository.
We start with the simplest, single window app that we can create that loads a UICollectionViewController from Main.storyboard.
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
}
Create a UICollectionViewController in the storyboard and make sure that Is Initial View Controller
is toggled on.
We will now conform to the UICollectionViewDataSource
to display a SelfSizingCollectionViewCell
.
class SelfSizingViewController: UICollectionViewController {
// MARK: - UICollectionViewDataSource
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "SelfSizingCollectionViewCell", for: indexPath) as? SelfSizingCollectionViewCell else {
return UICollectionViewCell()
}
cell.label1.text = "This is label one.\nThis is label one.\nThis is label one.\nThis is label one.\nThis is label one."
cell.label2.text = "This is label two.\nThis is label two.\nThis is label two.\nThis is label two.\nThis is label two."
return cell
}
}
Configuring SelfSizingViewController on viewDidLoad
The first step in configuring Auto Layout to create Self Sizing UICollectionViewCells is to configure UICollectionViewFlowLayout
. This is done in the viewDidLoad
method in SelfSizingViewController
.
class SelfSizingViewController: UICollectionViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let flowLayout = collectionViewLayout as? UICollectionViewFlowLayout,
let collectionView = collectionView {
let w = collectionView.frame.width - 20
flowLayout.estimatedItemSize = CGSize(width: w, height: 200)
}
}
}
The above code calculates the width of a cell by taking the width of the entire collection view and subtracting 20 points for the margins.
Implementing SelfSizingCollectionViewCell
We create a SelfSizingCollectionViewCell
in the storyboard that has two UILabel
s as outlets.
The two labels are constrained to the edges of SelfSizingCollectionViewCell. And a vertical constraint is added between the top and bottom label. For the cell to self size based upon the contents of the labels, the second label needs a Vertical Content Hugging Priority
of 250.
preferredLayoutAttributesFitting
And finally in SelfSizingCollectionViewCell
, we must implement preferredLayoutAttributesFitting
class SelfSizingCollectionViewCell: UICollectionViewCell {
@IBOutlet var label1: UILabel!
@IBOutlet var label2: UILabel!
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
setNeedsLayout()
layoutIfNeeded()
let size = contentView.systemLayoutSizeFitting(layoutAttributes.size)
var frame = layoutAttributes.frame
frame.size.height = ceil(size.height)
layoutAttributes.frame = frame
return layoutAttributes
}
}