UICollectionView in landscape on iPhone X
Asked Answered
P

6

45

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.

Profundity answered 16/9, 2017 at 21:9 Comment(6)
Note that it’s recemmended NOT to inset the entire collection view content. Section headers should stretch edge-to-edge for example. That’s accomplished using section insets on the layout instead of the content insets on the collection view.Sanctitude
Ahh, yes, good catch. My initial test setup was a header-less single section so I wasn't thinking about that.Profundity
@Joey Why should section headers not be inset to safe area? The text in the section title is hidden by the notch in landscape.Forland
@Forland The contents of the header should be inset properly, but the header view itself should not be inset, thereby allowing its background to stretch edge to edge.Sanctitude
@Joey Okay, the content is not inset properly for me, I have to double check that.Forland
@Joey Thanks that led me to fix it. I had been using the uitableviewdelegate method viewForHeaderInSection to implement my own layout. I needed to constrain the leading anchor of my contained UILabel to mycustomheaderview.layoutMarginsGuide.leadingAnchor. This was kind of tricky, never done that in code before.Forland
P
42

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.

Profundity answered 18/9, 2017 at 16:29 Comment(11)
Great find. I'm amazed that more people haven't come across this issue. However, I am not able to reproduce the bug you mentioned. When I rotate back to Portrait after being in Landscape my insets are correct.Mission
Huh, interesting. The test project I used for the screenshots (and submitted with my bug report) was pretty minimal, and I think would reflect the most basic default behaviours... I haven't received a reply from Apple yet. There's certainly plenty of other buggy behaviours with the iPhone X simulator... in any case, I expect that by the time the iPhone X is actually released, we'll have iOS 11.1 and hopefully these issues will have been fixed.Profundity
@WesCampaigne please, could you update your answer? The images links are dead.Proconsulate
Images are still working for me? They're hosted using stack overflow's integrated image stuff.Profundity
How did you get your headers to have a full width background?Subtotal
The collection view itself is the full screen width, and not confined within the safe area guides. Meanwhile, I anchored the leading edge of the header's label to the superview's leading margin; the margin automatically adapts to the safe area, so the label is appropriately inset when needed.Profundity
nice and to get collection view width to calculate cell width you can use CGFloat collectionViewWidth = CGRectGetWidth(self.collectionView.frame) - (self.collectionView.safeAreaInsets.left + self.collectionView.safeAreaInsets.right);Backplate
well, there IS a bug with the safeAreaInsets after rotation on the iPhone X - the case is start the app - go to landscape - put app to background - activate app again - turn back to portrait. This leaves the safeAreaInsets still in landscape mode. Didn't find a solution yet ...Evangelineevangelism
Ok, found it - the answer from petrsyn pointed me the way - in my case I had to set the contentInsetAdjustmentBehavior of the scroll view I 'm using to .never to prevent it from using the insets at all (it's never rotated and shown behind a video view that can be rotated)Evangelineevangelism
@WesCampaigne How did you solve the bug with the wrong insets after rotation? Seeing the same issue.Cassaundra
@WesCampaigne I faced the same issue. It's difficult to adapt rotation on iPone X (XS, XS Max). Have you solved this problem?Perjure
L
71

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.

Lees answered 11/10, 2017 at 15:6 Comment(6)
@Forland This solution worked better than the accepted answer in my case because it also works as expected if the phone is rotated.Lees
Yes I also ended up using this for now. It's nice because the setting can be set in viewDidLoad which felt more convenient as a one-time solution.Forland
This works for me, but when I enter the VC with collection view in landscape, it aligns all cells to the left, and I need to rotate to portrait and back. Tried invalidating layout, etc, but nothing works. Did you encounter such behavior?Urethroscope
@ŁukaszPrzytuła No I din't encounter this. Did you experience it when you set the collectionView?.contentInsetAdjustmentBehavior = .always and it works as expected otherwise?Lees
Already managed to fix it. Found some code scrolling to bottom, which changed contentOffset with x hardcoded to 0.Urethroscope
FYI, when the contentInsetAdjustmentBehavior property is set to a value that accounts for the safe area, you can find what these insets have been adjusted to by checking the UICollectionView's adjustedContentInset property. developer.apple.com/documentation/uikit/uiscrollview/…Rescind
P
42

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.

Profundity answered 18/9, 2017 at 16:29 Comment(11)
Great find. I'm amazed that more people haven't come across this issue. However, I am not able to reproduce the bug you mentioned. When I rotate back to Portrait after being in Landscape my insets are correct.Mission
Huh, interesting. The test project I used for the screenshots (and submitted with my bug report) was pretty minimal, and I think would reflect the most basic default behaviours... I haven't received a reply from Apple yet. There's certainly plenty of other buggy behaviours with the iPhone X simulator... in any case, I expect that by the time the iPhone X is actually released, we'll have iOS 11.1 and hopefully these issues will have been fixed.Profundity
@WesCampaigne please, could you update your answer? The images links are dead.Proconsulate
Images are still working for me? They're hosted using stack overflow's integrated image stuff.Profundity
How did you get your headers to have a full width background?Subtotal
The collection view itself is the full screen width, and not confined within the safe area guides. Meanwhile, I anchored the leading edge of the header's label to the superview's leading margin; the margin automatically adapts to the safe area, so the label is appropriately inset when needed.Profundity
nice and to get collection view width to calculate cell width you can use CGFloat collectionViewWidth = CGRectGetWidth(self.collectionView.frame) - (self.collectionView.safeAreaInsets.left + self.collectionView.safeAreaInsets.right);Backplate
well, there IS a bug with the safeAreaInsets after rotation on the iPhone X - the case is start the app - go to landscape - put app to background - activate app again - turn back to portrait. This leaves the safeAreaInsets still in landscape mode. Didn't find a solution yet ...Evangelineevangelism
Ok, found it - the answer from petrsyn pointed me the way - in my case I had to set the contentInsetAdjustmentBehavior of the scroll view I 'm using to .never to prevent it from using the insets at all (it's never rotated and shown behind a video view that can be rotated)Evangelineevangelism
@WesCampaigne How did you solve the bug with the wrong insets after rotation? Seeing the same issue.Cassaundra
@WesCampaigne I faced the same issue. It's difficult to adapt rotation on iPone X (XS, XS Max). Have you solved this problem?Perjure
G
12

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
    }
}
Grubman answered 29/9, 2017 at 17:21 Comment(0)
R
1

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.

Romanticize answered 28/1, 2018 at 20:39 Comment(0)
A
1

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)
])
Another answered 29/5, 2020 at 21:27 Comment(2)
Why was Kirills answer downvoted? Are there any pitfalls? In my case, his answer helped me to realize, that I can constrain the collectionView already in viewDidLoad to the safe area (and not in the UICollectionViewCell). This solved my problem, though my problem was a little different from the thread starters question. I will upvote Kirills answer unless somebody can show me the issues with his answer.Tabriz
@Tabriz the issue with his solution is that the collection view is clipped to the safe area. Collection view content should be able to scroll past safe area and into the view's boundary (the rectangle that contains all pixels on screen). The correct method is to inset content.Sauls
T
0

UICollectionView is intended to be flexible for a wide variety of layouts. The most common layouts are grids with multiple rows and columns, but it's possible to create non-grid layouts with UICollectionView.

UITableView, on the other hand, is designed for full-width cells.

Therefore, it makes sense that UITableView would have built-in support for dealing with safe area insets, since table view layouts will always be affected by it. Because UICollectionView uses custom layouts, it makes sense to solve these issues on a per-implementation basis instead of trying to provide a one-size-fits-all solution.

Truitt answered 16/9, 2017 at 21:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.