How to get updated safeAreaInsets in iOS

Issue #570

Use viewSafeArea

1
2
3
4
5
6
@available(iOS 11.0, *)
override func viewSafeAreaInsetsDidChange() {
super.viewSafeAreaInsetsDidChange()

self.collectionView.reloadData()
}

Use https://developer.apple.com/documentation/uikit/uiview/2891102-safearealayoutguide

1
view.safeAreaLayoutGuide.layoutFrame

https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets

For the view controller’s root view, the insets account for the status bar, other visible bars, and any additional insets that you specified using the additionalSafeAreaInsets property of your view controller. For other views in the view hierarchy, the insets reflect only the portion of the view that is covered. For example, if a view is entirely within the safe area of its superview, the edge insets in this property are 0.

Use UICollectionView.contentInsetAdjustmentBehavior

Nested view

For UICollectionView inside Cell inside UICollectionView, its insets is 0, but its parent parent is correct, which is the original cell

UICollectionView -> Cell -> ContentView -> UICollectionView

1
collectionView.superview?.superview?.safeAreaInsets

viewWillAppear: safeAreaInsets is not set to collectionView
viewDidAppear: safeAreaInsets is set to collectionView and cells, but not to nested collectionView

In viewSafeAreaInsetsDidChange, invalidate outer and nested collectionViewLayout

Use extendedLayoutIncludesOpaqueBars

https://developer.apple.com/documentation/uikit/uiviewcontroller/1621404-extendedlayoutincludesopaquebars

A Boolean value indicating whether or not the extended layout includes opaque bars.

Seems to affect left and right insets

But it’s not, it is because of when safeAreaInsets is available, and how it is passed to nested view

When invalidating collection view layout with custom UIPresentationController, alongsideTransition is called twice, the first time with old safeAreaInsets, and the second time with latest safeAreaInsets

And the layout invalidation uses the old insets.

1
2
3
4
5
6
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { _ in
self.collectionViewLayout.invalidateLayout()
})
}

Dispatch

1
2
3
4
5
6
7
8
9
// UIViewController subclass
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { _ in
DispatchQueue.main.async {
self.collectionViewLayout.invalidateLayout()
}
})
}

Call layoutIfNeeded

1
2
3
4
5
6
7
8
// UIPresentationController subclass
override var frameOfPresentedViewInContainerView: CGRect {
guard let containerView = containerView else { return .zero }

presentedView?.setNeedsLayout()
presentedView?.layoutIfNeeded()

return ...

Check

Check that UICollectionView or the view you’re working on is in view hierarchy

Check that you’re using code in viewDidLayoutSubviews when safeAreaInsets is known

Read more

Comments