The answer from Skoua might work in some situations, but does have certain side-effects on iOS11 and later. Most notably, the scroll view will start propagating safe areas to its children, which can mess up your layout while scrolling if you use the safe areas or layout margins.
Apple explains this very well and in detail in this WWDC session and also mentions explicitly that contentInsetAdjustmentBehavior = .never
can have side-effects and is not what you should use in most cases.
To get a scroll view that does not mess up our layout, but shows its content below the status bar (or navigation bar), you should observe the safe area of your scroll view and adjust your custom content insets accordingly:
private var scrollViewSafeAreaObserver: NSKeyValueObservation!
override func viewDidLoad() {
...
if #available(iOS 11.0, *) {
self.scrollViewSafeAreaObserver = self.scrollView.observe(\.safeAreaInsets) { [weak self] (_, _) in
self?.scrollViewSafeAreaInsetsDidChange()
}
} else {
self.automaticallyAdjustsScrollViewInsets = false
}
}
@available(iOS 11.0, *)
func scrollViewSafeAreaInsetsDidChange() {
self.scrollView.contentInset.top = -self.scrollView.safeAreaInsets.top
}
deinit {
self.scrollViewSafeAreaObserver?.invalidate()
self.scrollViewSafeAreaObserver = nil
}
Why does this work? Because we leave contentInsetAdjustmentBehavior = .automatic
. This will give us normal behaviour when it comes to scrolling and non-scrolling axis, but the UIScrollView will also "convert" any safe areas to content insets. Since we don't want this for our top edge, we simply set the negative top safe area as our custom insets, which will counter any insets set by the scroll view.