I have a UICollectionView using flow layout, and I'm trying to achieve the same margins as a UITableViewController with readable content width.
The closest I've come to matching this layout behavior is to embed a UICollectionViewController within a UIViewController and have the embedded view "Follow Readable Width".

Here the teal color is the UIViewController and the salmon color is the UICollectionViewController. The problem is the teal area doesn't scroll the UICollectionView and the scroll indicators are also not along the edge of the screen like you would expect.
My question is:
How can I achieve this layout without having to embed a
UICollectionViewController?
My guess is that I can somehow set the UICollectionView left and right section insets to match the readable content guide margins and update them by overriding viewWillTransition(to size: with coordinator:) and observing UIContentSizeCategoryDidChange notifications, but I'm not sure how to go about this.
In order to solve your problem with Swift 5.1 and iOS 13, you can set your flow layout's sectionInsetReference property to .fromContentInset, set your collection view's insetsLayoutMarginsFromSafeArea property to false and set your collection view's contentInsets to match the view's readableContentGuide's insets.
The following sample code shows a possible implementation in order to have a collection view that has a readable content width:
ViewController.swift
import UIKit
class ViewController: UIViewController {
let collectionView = UICollectionView(
frame: .zero,
collectionViewLayout: ColumnFlowLayout(cellsPerRow: 5)
)
var collectionViewNeedsLayout = true
override func viewDidLoad() {
super.viewDidLoad()
title = "Demo"
view.addSubview(collectionView)
collectionView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: view.topAnchor),
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
collectionView.backgroundColor = .white
collectionView.insetsLayoutMarginsFromSafeArea = false
collectionView.dataSource = self
collectionView.register(Cell.self, forCellWithReuseIdentifier: "Cell")
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
if collectionViewNeedsLayout {
let margin = view.readableContentGuide.layoutFrame.origin.x
collectionView.contentInset.left = margin
collectionView.contentInset.right = margin
collectionViewNeedsLayout = false
}
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
collectionViewNeedsLayout = true
}
}
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 59
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! Cell
return cell
}
}
ColumnFlowLayout.swift
import UIKit
class ColumnFlowLayout: UICollectionViewFlowLayout {
let cellsPerRow: Int
init(cellsPerRow: Int) {
self.cellsPerRow = cellsPerRow
super.init()
self.sectionInsetReference = .fromContentInset
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func prepare() {
super.prepare()
guard let collectionView = collectionView else { return }
let marginsAndInsets = collectionView.contentInset.left + collectionView.contentInset.right + sectionInset.left + sectionInset.right + minimumInteritemSpacing * CGFloat(cellsPerRow - 1)
let itemWidth = ((collectionView.bounds.width - marginsAndInsets) / CGFloat(cellsPerRow)).rounded(.down)
itemSize = CGSize(width: itemWidth, height: itemWidth)
}
}
Cell.swift
import UIKit
class Cell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
contentView.backgroundColor = .orange
}
required init?(coder: NSCoder) {
fatalError("not implemnted")
}
}
Display on iPhone 11 Pro Max:

On iOS 14+ with a diffable data source, you can use section.contentInsetsReference = .readableContent.
This will adjusts the section insets and follows the readable guide.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With