Proper embedding in the world of increasingly diverse safeAreaInsets
It’s a funny title, eh? Born out of half-a-day of head-scratching.
I wrote about embedding child view controllers before; it’s a topic that’s really dear to me. Employed properly, it helps to nicely compartmentalize complex UIs. This was the final recommended approach:
final class CustomContainerController: UIViewController {
func display(vc: UIViewController) {
embeddedController = vc
if !isViewLoaded { return }
embedIfNeeded()
}
override func viewDidLoad() {
super.viewDidLoad()
embedIfNeeded()
}
private func embedIfNeeded() {
guard let vc = embeddedController else { return }
embed(controller: vc, into: containingView)
}
}
One thing that often got me with this technique is accounting for safe areas. Ideally, I want to just embed the controller’s view and have its content layout magically inside the safe areas of the device.
For that to work, a few common sense rules need to apply.
First — in all content UIViewControllers (meaning those you intent to embed inside some sort of root container controller) your Auto Layout constraints must be set towards superview margins. Here’s an example where I have UILabel
leading constraint set to match superview’s leadingMargin:
Not safeArea.leading, not superview.leading — go for the superview.leadingMargin. This is what you should almost always do for content UI components like UILabel
, UIButton
that are attached to the UIViewController.view
. This should be the default you go with and deviate only in some very specific cases.
Second — embedIfNeeded
method now requires just one more line to make the magic work:
private func embedIfNeeded() {
guard let vc = embeddedController else { return }
embed(controller: vc, into: containingView)
vc.view.preservesSuperviewLayoutMargins = true
}
What preservesSuperviewLayoutMargins does is makes sure that whatever margins are set on the container view will be respected by the embedded child view.
Third — that containingView
(into which you embed contentVC.view) in the container controller should use safeArea as its margins:
Follow these guidelines and your content will always remain visible and accessible regardless of the device where the app is running.