Reputation: 6693
I have the issue that the topLayoutGuide.length
in a UIViewController
(from XIB) gets set after viewWillAppear
and i don't know how to hook into the change of topLayoutGuide.length
to initially set the contentOffset of a table view.
Code to modally present a UIViewController
inside a UINavigationController
:
let viewController = UIViewController(nibName: "ViewController", bundle: nil)
let navigationController = UINavigationController(rootViewController: viewController)
present(navigationController, animated: true, completion: nil)
My debugging output about the topLayoutGuide.length
Init view controller
-[UIViewController topLayoutGuide]: guide not available before the view controller's view is loaded
willMove toParentViewController - top layout guide nan
Init navigation controller and pass view controller as root vc
Present navigation controller modally
viewDidLoad - top layout guide 0.0
viewWillAppear - top layout guide 0.0
viewWillLayoutSubviews - top layout guide 64.0
viewDidLayoutSubviews - top layout guide 64.0
viewWillLayoutSubviews - top layout guide 64.0
viewDidLayoutSubviews - top layout guide 64.0
viewDidAppear - top layout guide 64.0
didMove toParentViewController - top layout guide 64.0
viewWillLayoutSubviews - top layout guide 64.0
viewDidLayoutSubviews - top layout guide 64.0
For now i use a bool flag in the view controller to set the contentoffset in the viewDidLayoutSubviews
only once, even though the method is called multiple times.
Any more elegant solution in mind?
Upvotes: 3
Views: 691
Reputation: 17261
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.
... 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()
).
The only way to do some initial layout tweaks that depend on the layout guide's length is the method you suggested yourself:
Have a boolean property in your view controller that you set to true
initially:
var isInitialLayoutPass: Bool = true
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)
}
}
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.
Upvotes: 4