iosdev

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.