As simple as it might be, I still find my self struggling with the correct solution.
I'm trying to understand what is the correct way to find the REAL UIView
(or any other subview) frame inside viewDidLoad
when using Auto Layout.
The main issue is that in viewDidLoad, the views aren't applied their constraints. I know that the "known" answer for this situation is
override func viewDidLoad() {
super.viewDidLoad()
view.layoutIfNeeded()
stepContainer.layoutIfNeeded() // We need this Subview real frame!
let realFrame = stepContainer.frame
}
But I found out that it's not ALWAYS working, and from time to time it give's wrong frame (ie not the final frame that is displayed).
After some more researching I found that warping this code under DispatchQueue.main.async { }
gives accurate result. But I'm not sure if it's the correct way to handle that, or am I causing some kind of under-the-hood issues using this. Final "working" code:
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.main.async {
self. stepContainer.layoutIfNeeded()
print(self. stepContainer.frame) // Real frame..
}
}
NOTE : I need to find what is the real frame only from viewDidLoad, please don't suggest to use viewDidAppear/layoutSubviews etc.
As @DavidRönnqvist pointed out
The reason dispatch async gives you the "correct" value here is that it get scheduled to run in the next run loop; after
viewWillAppear
andviewDidLayoutSubviews
has run.
Example
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.main.async {
print("DispatchQueue.main.async viewDidLoad")
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("viewWillAppear")
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("viewDidAppear")
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
print("viewDidLayoutSubviews")
}
viewWillAppear
viewDidLayoutSubviews
viewDidAppear
DispatchQueue.main.async viewDidLoad
Code inside DispatchQueue.main.async
from viewDidLoad
even is called after viewDidAppear
. So using DispatchQueue.main.async
in viewDidLoad
gives you right frame but it isn't earliest as possible.
Answer
If you want to get right frame as early as possible, viewDidLayoutSubviews
is the correct place to do it.
If you have to put some code inside viewDidLoad
, you are doing right way. Seem like DispatchQueue.main.async
is the best way to do it.
Your question is similar to this problem, and my answer will also be the same.
The frame is not guaranteed to be the same in viewDidLoad as it will be when the view is eventually displayed. UIKit adjusts the frame of your view controller's view before presenting it, based on the context in which will appear. For a better understanding of view lifecycle, you can refer this image.
The image will help you to understand why your code is working. As the picture shows that viewWillAppear gets called once a view is loaded and when you set up dispatch async it is added to the thread asynchronously after the execution of viewWillAppear. So, once viewWillAppear is called your frames are updated as per your view, and you get the correct frame in dispatch async.
References: Image source
For more information about view life cycle you can visit this answer
So, at last, you can go for either of two options:
Hope this helps!
After reading the comments, maybe you can try to embed your view controller into another one with a container and do something like this:
var viewSize : CGSize?
var parentViewSize : CGSize?
viewDidLayoutSubViews
and store it: viewSize = view.size
prepareForSegue
send the size you stored: (destinationViewController as? MyViewController)?.parentViewSize = viewSize
viewDidLoad
you will access the parentViewSize
variable with the good value in itWould it do it?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With