Search code examples
iosuicollectionviewuikitios11iphone-x

UICollectionView in landscape on iPhone X


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.


Solution

  • While Nathan is correct about the versatility of UICollectionView with various layouts, I was mainly concerned about the "default" case where one is using UICollectionViewFlowLayout.

    Turns out, iOS 11 has added a sectionInsetReference property to UICollectionViewFlowLayout. The official documentation on it currently lacks a description, however the headers describe it as

    The reference boundary that the section insets will be defined as relative to. Defaults to .fromContentInset.

    NOTE: Content inset will always be respected at a minimum. For example, if the sectionInsetReference equals .fromSafeArea, but the adjusted content inset is greater that the combination of the safe area and section insets, then section content will be aligned with the content inset instead.

    The possible values are

    @available(iOS 11.0, *)
    public enum UICollectionViewFlowLayoutSectionInsetReference : Int {
        case fromContentInset
        case fromSafeArea
        case fromLayoutMargins
    }
    

    and setting it to .fromSafeArea produces the desired results, i.e., when initially in portrait orientation:

    initial portrait layout

    then when rotating to landscape, the cells are inset such that they are entirely within the safe area:

    iPhone X landscape collection view layout

    ... HOWEVER, there's currently a bug, and when rotating back to portrait after the view has been in landscape, it continues to act as if the left/right safeAreaInsets are set to the landscape values:

    portrait layout following rotation from landscape

    I've filed a radar (rdar://34491993) regarding this issue.