I have a custom UICollectionViewFlowLayout that centers each cell. To do this, I overrode targetContentOffset, which the collection view calls whenever the user lifts up their finger. However, once I rotate the device, the cells get off center — targetContentOffset is not called.
| Normally, cells are centered | After rotating the device, cells are no longer centered |
|---|---|
![]() |
![]() |
Note 1: After rotating, just swipe a bit on the cells, and they bounce back to center...
Note 2: This gets printed in my console:
2021-11-16 21:37:54.979021-0800 TargetContentOffsetTest[30817:356789] [UICollectionViewRecursion] cv == 0x12f02d400 Disabling recursion trigger logging
Here's my code (demo repo):
class PagingFlowLayout: UICollectionViewFlowLayout {
var layoutAttributes = [UICollectionViewLayoutAttributes]() /// custom attributes
var contentSize = CGSize.zero /// the scrollable content size of the collection view
override var collectionViewContentSize: CGSize { return contentSize } /// pass scrollable content size back to the collection view
/// pass attributes to the collection view flow layout
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { return layoutAttributes[indexPath.item] }
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { return layoutAttributes.filter { rect.intersects($0.frame) } }
override func prepare() {
super.prepare()
guard let collectionView = collectionView else { return }
let cellWidth = collectionView.bounds.width
let cellHeight = collectionView.bounds.height
var layoutAttributes = [UICollectionViewLayoutAttributes]()
var currentCellOrigin = CGFloat(0) /// used for each cell's origin
for index in 0..<3 { /// hardcoded, but only for now
let attributes = UICollectionViewLayoutAttributes(forCellWith: IndexPath(item: index, section: 0))
attributes.frame = CGRect(x: currentCellOrigin, y: 0, width: cellWidth, height: cellHeight)
layoutAttributes.append(attributes)
currentCellOrigin += cellWidth
}
self.contentSize = CGSize(width: currentCellOrigin, height: cellHeight)
self.layoutAttributes = layoutAttributes
}
/// center the cell
/// this is called when the finger lifts, but NOT when the device rotates!
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
let contentOffset = collectionView?.contentOffset.x ?? 0
let closestPoint = layoutAttributes.min { abs($0.frame.origin.x - contentOffset) < abs($1.frame.origin.x - contentOffset) }
return closestPoint?.frame.origin ?? proposedContentOffset
}
}
class ViewController: UIViewController, UICollectionViewDataSource {
lazy var collectionView: UICollectionView = {
let flowLayout = PagingFlowLayout()
flowLayout.scrollDirection = .horizontal
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: flowLayout)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.decelerationRate = .fast
collectionView.dataSource = self
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
view.addSubview(collectionView)
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: view.topAnchor),
collectionView.leftAnchor.constraint(equalTo: view.leftAnchor),
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
collectionView.rightAnchor.constraint(equalTo: view.rightAnchor)
])
return collectionView
}()
let colors: [UIColor] = [.red, .green, .blue]
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return 3 }
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
cell.contentView.backgroundColor = colors[indexPath.item]
return cell
}
override func viewDidLoad() {
super.viewDidLoad()
_ = collectionView /// setup
}
}
How can I make the collection view call targetContentOffset after a bounds change/rotation event? What is the correct way to ensure my cells always stay centered — is there something automatic I can use, or should I subscribe to viewDidLayoutSubviews back in my view controller and manually call setContentOffset(_:animated:)?

