How to use additionalSafeAreaInsets

I'm trying to implement additionalSafeAreaInsets, similar to the example from the Apples documentation, but the child view controller is not respecting the additional insets:

Code Block swift
import UIKit
import PlaygroundSupport
class MyVC: UIViewController {
var leftView: UIView!
let secondVC = SecondVC()
override func viewDidLoad() {
super.viewDidLoad()
leftView = UIView(frame: CGRect(origin: .zero, size: .init(width: 50, height: self.view.bounds.size.height)))
leftView.backgroundColor = .blue
self.view.addSubview(leftView)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
var newSafeArea = UIEdgeInsets()
if let sideViewWidth = leftView?.bounds.size.width {
newSafeArea.left += sideViewWidth
}
print(newSafeArea)
addChild(secondVC)
secondVC.view.frame = self.view.safeAreaLayoutGuide.layoutFrame
self.view.addSubview(secondVC.view)
secondVC.didMove(toParent: self)
secondVC.additionalSafeAreaInsets = newSafeArea
}
}
class SecondVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .yellow
}
}
PlaygroundPage.current.needsIndefiniteExecution = true
PlaygroundPage.current.liveView = MyVC()


The expectation is that the left column view (leftView) is juxtaposed to a child view controller (childVC) within a root view controller (MyVC), but the child view controller simply takes up the entire main view.
Answered by OOPer in 646389022
So, this code is tested in the Playground of Version 12.2 (12B45b), but the behavior would not be affected by minor versions.

Code Block
import UIKit
import PlaygroundSupport
class MyVC: UINavigationController {
var leftView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
leftView = UIView(frame: CGRect(origin: .zero, size: .init(width: 50, height: self.view.bounds.size.height)))
leftView.backgroundColor = UIColor.blue.withAlphaComponent(0.5)
self.view.addSubview(leftView)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
var newSafeArea = UIEdgeInsets()
if let sideViewWidth = leftView?.bounds.size.width {
newSafeArea.left += sideViewWidth
}
print(newSafeArea)
children[0].additionalSafeAreaInsets = newSafeArea
}
}
class SecondVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .yellow
let subview = UIView(frame: CGRect(origin: .zero, size: self.view.frame.size))
subview.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(subview)
subview.backgroundColor = .green
NSLayoutConstraint.activate([
subview.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
subview.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
subview.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
subview.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
])
}
}
PlaygroundPage.current.needsIndefiniteExecution = true
let theVC = MyVC(rootViewController: SecondVC())
PlaygroundPage.current.liveView = theVC


You can experiment by modifying newSafeArea or commenting out line 24.


The code in the Apples doc uses childViewControllers, but it is renamed to children in Swift. Seems the code is a little bit outdated. But it is not a big issue here.

The property children makes sense only in a container view controller.
That means the example code is intended to be used in some sort of container view controllers, such as UINavigationController or UITabBarController,
to affect the layout of the content view controller using constraints.
additionalSafeAreaInset represents the increment or decrement, not the new value.

So, probably in your case

Code Block
secondVC.additionalSafeAreaInsets = UIEdgeInsets(top: 0, left: sideViewWidth, bottom: 0, right: 0)

I was actually copying the template provided by the Apple documentation. Also, I've tried your example, but still didn't work.
@Ovis

The expectation is that the left column view (leftView) is juxtaposed to a child view controller (childVC) within a root view controller (MyVC)

Sorry, but I do not understand why you think you can expect such behavior.
Setting additionalSafeAreaInsets of secondVC affects the autolayout of the content views in secondVC using constraints to safeArea.

It has nothing to do with the layout of the root view controller.

@OOPer

I understand what you're saying and that was initially what I thought as well, but even when I tried to impose the additional insets to MyVC, the super view controller, instead to SecondVC, it still doesn't provide the spacing.

So instead of:
Code Block swift
secondVC.additionalSafeAreaInsets = newSafeArea

I tried:
Code Block swift
self.additionalSafeAreaInsets = newEdgeInsets

it made no difference.

I tried keeping the additionalSafeAreaInsets to SecondVC and adding a child view controller, AnotherVC, to SecondVC to see if it provides the insets, but didn't:

Code Block swift
class SecondVC: UIViewController {
  let anotherVC = AnotherVC()
  override func viewDidLoad() {
    super.viewDidLoad()
     
    self.view.backgroundColor = .yellow
    addChild(anotherVC)
    self.view.addSubview(anotherVC.view)
    anotherVC.didMove(toParent: self)
         
  }
}
class AnotherVC: UIViewController {
  override func viewDidLoad() {
    super.viewDidLoad()
    self.view.backgroundColor = .purple
  }
}


As far as I understand, safe areas, unlike regular margins, are imposed by a view controller and propagated down the view hierarchy or the child view controller hierarchy. This is intentional so that if there is a top bar or a bottom tab bar, all the subviews and child VC's will also be safe from being covered by those bars. So it was my assumption that when we are adding the safe area, we are adding to the existing one from the parent view controller.

Apple's example from this documentation also shows that the additional insets are being added to the child view controller to achieve what I'm trying to achieve:

Code Block swift
override func viewDidAppear(_ animated: Bool) {
  var newSafeArea = UIEdgeInsets()
  // Adjust the safe area to accommodate
  // the width of the side view.
  if let sideViewWidth = sideView?.bounds.size.width {
    newSafeArea.right += sideViewWidth
  }
  // Adjust the safe area to accommodate
  // the height of the bottom view.
  if let bottomViewHeight = bottomView?.bounds.size.height {
    newSafeArea.bottom += bottomViewHeight
  }
  // Adjust the safe area insets of the
  // embedded child view controller.
  let child = self.childViewControllers[0]
  child.additionalSafeAreaInsets = newSafeArea
}

Apple's example from this documentation also shows that the additional insets are being added to the child view controller 

YES.

to achieve what I'm trying to achieve:

NO. The code you have shown is intended to make safeArea of the child view controller smaller than usual. It does not intended to modify the layout of the parent view controller.

Anyway, additionalSafeAreaInsets works for constraints made with safeArea and subviews.
In your code, there are no constraints.

Also layoutMargins (or directionalLayoutMargins as well) works for constraints with edges and subviews.
In your code, there are no constraints.
What is shown in the Apple documentation is in fact what I'm trying to do where there is one parent view controller and a child controller along with another view. The child view controller and the view are next to each other within the parent view controller set by additionalSafeAreaInsets.

I've also tried applying the constraints, but still doesn't work:

Code Block swift
    secondVC.view.translatesAutoresizingMaskIntoConstraints = false
    NSLayoutConstraint.activate([
      secondVC.view.leadingAnchor.constraint(equalTo: self.leftView.safeAreaLayoutGuide.trailingAnchor),
      secondVC.view.heightAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.heightAnchor),
      secondVC.view.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor)
    ])


or

Code Block swift
    secondVC.view.translatesAutoresizingMaskIntoConstraints = false
    NSLayoutConstraint.activate([
      secondVC.view.leadingAnchor.constraint(equalTo: self.leftView.trailingAnchor),
      secondVC.view.heightAnchor.constraint(equalTo: self.view.heightAnchor),
      secondVC.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor)
    ])

Maybe I'm not understanding Apple's example clearly. Could you explain why the example applies additionalSafeAreaInsets to the child view controller?

I've also tried applying the constraints, but still doesn't work:

You are using secondVC as just a dumb container of view. Many of the features of view controller do not work in such an odd usage.

Could you explain why the example applies additionalSafeAreaInsets to the child view controller?

The example code is trying to affect the layout of the child view controller.
I understand where you're coming from, but that's not the point of this discussion. Also, I think view controller containment utilizes more than using a view controller as a dumb container.

Anyways, would you be able to try running the code in your Playground? I've formatted it so that it can work in Playground.

Anyways, would you be able to try running the code in your Playground?

As far as I see your code the code you have shown is working as I expect.
Please wait till App Store updates my Xcode. I cannot open Xcode and write some example till then.
Accepted Answer
So, this code is tested in the Playground of Version 12.2 (12B45b), but the behavior would not be affected by minor versions.

Code Block
import UIKit
import PlaygroundSupport
class MyVC: UINavigationController {
var leftView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
leftView = UIView(frame: CGRect(origin: .zero, size: .init(width: 50, height: self.view.bounds.size.height)))
leftView.backgroundColor = UIColor.blue.withAlphaComponent(0.5)
self.view.addSubview(leftView)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
var newSafeArea = UIEdgeInsets()
if let sideViewWidth = leftView?.bounds.size.width {
newSafeArea.left += sideViewWidth
}
print(newSafeArea)
children[0].additionalSafeAreaInsets = newSafeArea
}
}
class SecondVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .yellow
let subview = UIView(frame: CGRect(origin: .zero, size: self.view.frame.size))
subview.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(subview)
subview.backgroundColor = .green
NSLayoutConstraint.activate([
subview.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
subview.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
subview.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
subview.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
])
}
}
PlaygroundPage.current.needsIndefiniteExecution = true
let theVC = MyVC(rootViewController: SecondVC())
PlaygroundPage.current.liveView = theVC


You can experiment by modifying newSafeArea or commenting out line 24.


The code in the Apples doc uses childViewControllers, but it is renamed to children in Swift. Seems the code is a little bit outdated. But it is not a big issue here.

The property children makes sense only in a container view controller.
That means the example code is intended to be used in some sort of container view controllers, such as UINavigationController or UITabBarController,
to affect the layout of the content view controller using constraints.
Thank you so much!
How to use additionalSafeAreaInsets
 
 
Q