I am calling this in viewWillAppear
You must call it in viewDidAppear, it's that simple.
This is a classic "gotchya" !
PaulW11 already gave the answer, I am just putting it here to help googlers.
In viewWillAppear, obviously enough the view has not yet appeared but it will.
In viewDidAppear, the view has in fact literally appeared. That means that, absolutely, without a doubt, everything is there on the screen and of course, thus, completely laid-out.
As a rule if you're a hobbyist iOS programmer, just as a "general rule" only ever use viewDidAppear.
That is the "everyday solution" in 99% of cases.
It is fair to say viewWillAppear and viewDidLayoutSubviews and viewDidLoad are only for professional use. "Nothing bad will happen" other than the most subtle irrelevant, eg, performance effects, if you just use viewDidAppear. So, quite simply, always use viewDidAppear.
Regarding the subtle differences between viewDidLayoutSubviews, etc, it's really a big question that varies slightly from version to version and with specific arcane details of autolayout etc.; it is beyond the scope of this QA.
I'd almost say Apple shouldn't have exposed so prominently viewDidLayoutSubviews and viewWillAppear and viewDidLoad to the "general public". For example, if you're a beginner or hobbyist programmer, there is just no need at all to use things like CALayer, or to pass touches around, build your own gestures, manipulate transition animations etc.
Similarly, there is really just zero need to ever use viewDidLayoutSubviews or viewWillAppear or viewDidLoad. Just use "viewDidAppear" (in the few cases you eve need it) and you're all set.
BTW it's worth noting that if you just use container views, which you should be using everywhere at all times, (here's an epic tutorial on container views!!) these sort of UI racetrack problems rarely come up.