Let me start off by saying that this Medium post explains the new APIs better than I could, complete with pretty gifs and lots of screenshots and sample code. What I’m more interested in right now, though, is how we’re expected to go from iOS 10 APIs to iOS 11, not necessarily how to use iOS 11 out-of-the-box.
If we’re supporting an iOS 10 app right now, how do we upgrade it to become an iOS 11 app? What properties have been deprecated and what should I use instead? What’s different about them?
What if we need to support both iOS 10 and iOS 11, or we’re submitting an iOS 10 app compiled with the Xcode 9/iOS 11 SDK?
I wanted to put together a guide explaining the iOS 11 equivalents for a few things I’ve discussed previously (extended layout, layout guides) and what sort of pitfalls we may run into if our iOS 10 app needs to be compiled with the new SDK. When appropriate I’ll briefly touch on things I’ve explained in other posts, but I won’t go in depth on things I’ve already described previously.
UIViewController.topLayoutGuide / UIViewController.bottomLayoutGuide
iOS 10 functionality
UIViewController’s topLayoutGuide was introduced in iOS 7 when translucency was rolled out by default in navigation bars. Translucency meant that views should extend behind navigation bars, but content, for obvious reasons, should still start below. Apple accomplished this distinction by adding in UIViewController’s topLayoutGuide, which specifies where the visible content of a view controller’s view should begin. UIViewController.bottomLayoutGuide accomplished the same thing for the bottom of views, accounting for tab bars and toolbars instead of navigation bars and the status bar.
iOS 11 replacement
When safe areas were rolled out in iOS 11, both topLayoutGuide and bottomLayoutGuide were deprecated. In their place, UIViews — that is, not UIViewControllers — gained a new safeAreaLayoutGuide, a UILayoutGuide subclass (which top/bottomLayoutGuides were not). There was no need for a leftLayoutGuide or rightLayoutGuide before the iPhone X; if you rotated your device, you still only had to account for the status bar/navigation bar/toolbar/tab bar. The iPhone X, however, has physical considerations: the “notch” at the top is physically fixed to the top edge of the phone, so you’ll have space to account for on the top, bottom, left, or right edges depending on which way the device is rotated. iPads are expected to move towards a notchful design as soon as WWDC, so it’s important that developers start taking this into consideration.
Considerations
The spacing on each side of UIView.safeAreaLayoutGuide can be read from UIView.safeAreaInsets, and can be additionally increased by UIViewController.additionalSafeAreaInsets. We can (and should) set this whenever we have additional area that our views should be aware of that our content could be stuck behind. We can think of safeAreaInsets for a given view controller as answering “how much of my content is covered by my parent?” and setting additionalSafeAreaInsets as “how much of my content is covering my subview?”. We also know that safeAreaInsets.top is equal to the difference between the topAnchor and safeAreaLayoutGuide.topAnchor. Putting that together, the gap between topAnchor and safeAreaLayoutGuide.topAnchor is the amount of content our parent has that obscures our view. This is visualized below:
With a decent grasp of topLayoutGuides and bottomLayoutGuides, understanding safeAreaLayoutGuides is a relative breeze, and lets us look at all of the pieces in tandem and understand it. All of the following should make sense at this point — again, the difference between a view’s topAnchor and its safeAreaLayoutGuide.topAnchor is the amount of content that parent views (or parent notches and home indicators!) are obscuring:
As part of this, Apple also added two new methods: safeAreaInsetsDidChange() on UIView and viewSafeAreaInsetsDidChange() on UIViewController, both of which should be self-explanatory.
UIVC.automaticallyAdjustsScrollViewInsets
iOS 10 functionality
Recall from my earlier post on extended layout that iOS 7 provided UIViewControllers a property, true by default, that would adjust their root view’s (if a scroll view) or their first subview’s (if a scroll view) contentInset to account for a difference in the top/bottomLayoutGuide and the top/bottomAnchor, allowing an extended layout to pull a view underneath translucent bars. This allowed content to scroll under translucent bars by default without too much headache (allegedly) from app developers.
iOS 11 replacement
iOS 11 deprecates automaticallyAdjustsScrollViewInsets, replacing it instead with a new property on UIScrollView itself (which makes more sense to me): UIScrollView.contentInsetAdjustmentBehavior, an enum which has four possible values:
- .scrollableAxes: if a scroll view is scrollable in a given direction (i.e. its contentSize is greater than its frame size or it has alwaysBounce[Vertical/Horizontal] set to true), that direction receives additional contentInset equal to the safeAreaInsets
- .automatic: if a scroll view would have previously been affected by automaticallyAdjustsScrollViewInsets, it will gain contentInsets equal to the safeAreaInsets vertically. Additionally, if the scroll view is horizontally scrollable, it will gain contentInsets equal to the horizontal safeAreaInsets.
- .never: contentInsets are not changed automatically
- .always: contentInset is always adjusted by safeAreaInsets
To visualize the above, see the previously linked post. It’s awesome in the gif department.
UIScrollView.contentInset
iOS 10 functionality
In iOS 10 (well, since iOS 2), setting and querying a scroll view’s contentInset referred to how much additional room was provided on any given edge for the scroll view’s content. ContentInset is fundamental to extended layout/translucency and safeArea when we want content to scroll under other content, such as a navigation bar or the iPhone X’s home indicator, but we still want the content to have a state where it can be read unobscured.
iOS 11 behavior
UIScrollView’s contentInset behaves quite a bit different in iOS 11 when it comes to the contentInsetAdjustmentBehavior, though it’s unchanged if you are not/were not using automaticallyAdjustsScrollViewInsets. Before, if we set our contentInset and UIKit automatically adjusted us to account for translucency, our contentInset would then be changed to {contentInset we set} + {contentInset Apple added}. Now, contentInset is only ever equal to what we set. A new UIScrollView property, adjustedContentInset, holds the “final” value, or what we would have referred to as the contentInset previously. If we have a scroll view with contentInsetAdjustmentBehavior taking effect, the amount of “additional room” (previously what we referred to as contentInset) we’re provided is {safeAreaInsets + contentInset}, and that total can be read from adjustedContentInset.
Probably the most UIScrollView in apps is UITableView, which handles insetting the contentView of UITableViewCells and UITableViewHeaderFooterViews automatically. This means that by default our cells/headers can extend behind the notch in landscape, but the contentView (what we should be adding all of our cell/header subviews to) is inset by the safeAreaInsets so everything is still visible. This behavior is controlled by the new insetsContentViewsToSafeArea property on UITableView and defaults to true.
In Summary
- UIViewController.topLayoutGuide and UIViewController.bottomLayoutGuide have been deprecated in favor of UIView’s safeAreaLayoutGuide.
- UIView.safeAreaInsets can be checked to see how much spacing is being given to the safe area layout guides. If we want to tell UIKit that we have additional safeArea to account for, we can set UIView.additionalSafeAreaInsets.
- UIViewController.automaticallyAdjustsScrollViewInsets has been deprecated in favor of a UIScrollView-specific method contentInsetAdjustmentBehavior. There are four behaviors we can choose from: .automatic (default), .never, .scrollableAxes, and .always.
- In iOS 11, UIScrollView.contentInset now only refers to contentInset we set ourselves on the scroll view; when UIKit additionally adds safeAreaInsets to our contentInset due to the scroll view’s contentInsetAdjustmentBehavior, the final value that’s used to display is in the new UIScrollView.adjustedContentInset property.
To interject a bit of personal opinion, I really like what Apple did in iOS 11 with everything above. There’s less “magic” now, and when Apple does perform some behind-the-scenes magic, it’s made explicit through separate properties and additional events that are raised.
Feel free to reach out to me on Twitter @Wailord if you have any comments or questions. :)