Toplayoutguide in Child View Controller

topLayoutGuide in child view controller

While this answer might be correct, I still found myself having to travel the containment tree up to find the right parent view controller and get what you describe as the "real topLayoutGuide". This way I can manually implement automaticallyAdjustsScrollViewInsets.

This is how I'm doing it:

In my table view controller (a subclass of UIViewController actually), I have this:

- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];

_tableView.frame = self.view.bounds;

const UIEdgeInsets insets = (self.automaticallyAdjustsScrollViewInsets) ? UIEdgeInsetsMake(self.ms_navigationBarTopLayoutGuide.length,
0.0,
self.ms_navigationBarBottomLayoutGuide.length,
0.0) : UIEdgeInsetsZero;
_tableView.contentInset = _tableView.scrollIndicatorInsets = insets;
}

Notice the category methods in UIViewController, this is how I implemented them:

@implementation UIViewController (MSLayoutSupport)

- (id<UILayoutSupport>)ms_navigationBarTopLayoutGuide {
if (self.parentViewController &&
![self.parentViewController isKindOfClass:UINavigationController.class]) {
return self.parentViewController.ms_navigationBarTopLayoutGuide;
} else {
return self.topLayoutGuide;
}
}

- (id<UILayoutSupport>)ms_navigationBarBottomLayoutGuide {
if (self.parentViewController &&
![self.parentViewController isKindOfClass:UINavigationController.class]) {
return self.parentViewController.ms_navigationBarBottomLayoutGuide;
} else {
return self.bottomLayoutGuide;
}
}

@end

Hope this helps :)

How to set topLayoutGuide position for child view controller

As far as I have been able to tell after hours of debugging, the layout guides are readonly, and derived from the private classes used for constraints based layout. Overriding the accessors does nothing (even though they are called), and it's all just craptastically annoying.

Layout not working when container view controller dynamically changes child view controller

Asteroid is on the right track, but couple other issues...

You are not giving the views of the child controllers any constraints, so they load at their "native" size.

Changing your addViewController(...) func as advised by Asteroid solves the A and B missing constraints, but...

You are calling that same func for your container controller and adding constraints to its view in layout(), so you end up with conflicting constraints.

One solution would be to change your addViewController func to this:

func addViewController(_ child: UIViewController, constrainToSuperview: Bool = true) {
addChild(child)
view.addSubview(child.view)

if constrainToSuperview {
child.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
child.view.topAnchor.constraint(equalTo: view.topAnchor),
child.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
child.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
child.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
}

child.didMove(toParent: self)
}

then in setup():

func setup() {

titleBarView.backgroundColor = .red
view.addSubview(titleBarView)

// change this
//addViewController(container)

// to this
addViewController(container, constrainToSuperview: false)

showViewControllerA()
}

while leaving your other "add view controller" calls like this:

container.addViewController(viewControllerA)
container.addViewController(viewControllerB)

The other thing that may throw you off is the extraneous super. in your image view constraints:

    NSLayoutConstraint.activate([
// change this
//imageView.centerYAnchor.constraint(equalTo: super.view.centerYAnchor),
//imageView.centerXAnchor.constraint(equalTo: super.view.centerXAnchor),
//imageView.heightAnchor.constraint(equalTo: super.view.heightAnchor, multiplier: 0.6),

// to this
imageView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
imageView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
imageView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.6),
])

topLayoutGuide applied after viewWillAppear

The documentation for the topLayoutGuide states explicitly:

Query this property within your implementation of the viewDidLayoutSubviews() method.

Judging from your own inspections the earliest point to obtain the topLayoutGuide's actual length is inside the viewWillLayoutSubviews() method. However, I would not rely on that and do it in viewDidLayoutSubviews() as the docs suggest.

The reason why you cannot access the property earlier...

... is that the layout guides are objects that depend on the layout of any container view controllers. The views are laid out lazily when they are needed on screen. So when you add the viewController to the navigationViewController as its root view controller it's not laid out yet.

The layout happens when you present the navigationController. At that point the views of both view controllers are loaded (→ viewDidLoad(), viewWillAppear()) and then a layout pass is triggered. First, the navigationViewController's view is laid out (layout flow: superview → subview). The navigation bar's frame is set to a height of 64 px. Now the viewController's topLayoutGuide can be set. And finally the viewController's view is laid out (→ viewWillLayoutSubviews(), viewDidLayoutSubviews()).

Conclusion:

The only way to do some initial layout tweaks that depend on the layout guide's length is the method you suggested yourself:

  1. Have a boolean property in your view controller that you set to true initially:

    var isInitialLayoutPass: Bool = true 
  2. Inside viewDidLayoutSubviews() check for that property and only perform your initial layout when it's true:

    func viewDidLayoutSubviews() {
    if isInitialLayoutPass {
    tableView.contentOffset = CGPoint(x: 0, y: topLayoutGuide.length)
    }
    }
  3. Inside viewDidAppear(), set the property to false to indicate that the initial layout is done:

    override func viewDidAppear() {
    super.viewDidAppear()
    isInitialLayoutPass = false
    }

I know it still feels a little hacky but I'm afraid it's the only way to go (that I can think of) unless you want to use key-value-observing (KVO) which doesn't make it much neater in my opinion.

subview vs child view controller z position

Found the bug.

Other views had bringSubViewToFront / layer.zPosition,
some of them inside viewDidLayoutSubview, after I disable this code, everything works O.K

What confused me is the view debugger show the view on top,
but on the device it was hidden.



Related Topics



Leave a reply



Submit