UICollectionView in landscape on iPhone X

Viewed 16021

When iPhone X is used landscape, you're supposed to check safeAreaInsets to make suitably large gutters on the left and right. UITableView has the new insetsContentViewsToSafeArea property (default true) to automatically keep cell contents in the safe area.

I'm surprised that UICollectionView seems to not have anything similar. I'd expect that for a vertically-scrolling collection view, the left and right sides would be inset to the safe area when in landscape (and conversely, a horizontally-scrolling collection view would be inset if needed in portrait).

The simplest way to ensure this behaviour seems to be to add to the collection view controller:

- (void)viewSafeAreaInsetsDidChange {
    [super viewSafeAreaInsetsDidChange];
    UIEdgeInsets contentInset = self.collectionView.contentInset;
    contentInset.left = self.view.safeAreaInsets.left;
    contentInset.right = self.view.safeAreaInsets.right;
    self.collectionView.contentInset = contentInset;
}

... assuming contentInset.left/right are normally zero.

(NOTE: yes, for a UICollectionViewController, that needs to be self.view.safeAreaInsets; at the time this is called, the change to safeAreaInsets has oddly not yet propagated to self.collectionView)

Am I missing something? That boilerplate is simple enough, but it's effectively necessary now for every collection view that touches a screen edge. It seems really odd that Apple didn't provide something to enable this by default.

6 Answers

Having the same issue. This worked for me:

override func viewDidLoad() {
    if #available(iOS 11.0, *) {
        collectionView?.contentInsetAdjustmentBehavior = .always
    }
}

The documentation for the .always enum case says:

Always include the safe area insets in the content adjustment.

This solution works correctly also in the case the phone is rotated.

Thanks to above for help. For my UICollectionViewController subClass, I added the following to viewDidLoad() (Swift 3):

if let flowLayout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
    if #available(iOS 11.0, *) {
        flowLayout.sectionInsetReference = .fromSafeArea
    }
}

Bare in mind the above solutions do not solve the case where your collection cell views (and subviews that are constrained to its cell's leading or trailing edges) are full-width to the collection view bounds and aren't set to obey Safe Area Layout Guides.

Note: The answer by @petrsyn makes the header inset from the sides, which most people might not want and the answer by @Wes Campaigne doesn't really work correctly for full-width cells, where a subview is attached to the leading or trailing edges of the cell.

It's imperative, especially for those coming from older projects, to set your Xib and Storyboard files to Use Safe Area Layout Guides and then use auto layout to place constraints respective to the the safe areas or do similar in code.

If you're adding collection view as your main view's subview programmatically - then you can just do this in your viewDidLoad method:

collectionView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(collectionView)

NSLayoutConstraint.activate([
        collectionView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
        collectionView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
        collectionView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
        collectionView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor)
])
Related