Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AutoLayout: UIView within UIView has incorrect frame

I am working with AutoLayout in Storyboard, and everything seemed to be going fine. However, when I put one UIView within another and apply all the constraints I want to both the container and its children, I notice that the frame is incorrect in viewDidLayoutSubviews:. Rather than being the frame I would expect to be calculated given my constraints, it is some disgustingly large frame (albeit at the correct origin). For example, rather than a frame of {{26, 10}, {444, 10}} I get something like {{0, 0}, {320, 568}}.

Strangely enough, this only happens to the child UIView when I situate it within another UIView that has some constraints applied to it in relation to its superview (which is the view controller's view). As I am under the impression that I can expect my views to be properly laid out according to my constraints in the method viewDidLayoutSubviews:, I am confused as to why this is happening.

Am I making any incorrect assumptions? I would appreciate it if someone might help point me in the right direction. Thanks!

Note: This problem is completely fixed if I do my correct-frame-dependent setup in viewDidAppear:, but this is a somewhat unsatisfying workaround for me.

like image 462
hellfireboy Avatar asked Feb 19 '15 08:02

hellfireboy


1 Answers

Auto Layout changes to bounds are not necessarily finished in -viewDidLayoutSubviews. As put in "Advance Auto Layout Toolbox":

Constraint-based layout is an iterative process. The layout pass can make changes to the constraints based on the previous layout solution, which again triggers updating the constraints following another layout pass.

The documentation for -viewDidLayoutSubviews notes with emphasis in the original:

However, this method being called does not indicate that the individual layouts of the view's subviews have been adjusted.

So layout and constraint-based iterations in the subviews could continue to adjust their frames, but if a ViewController's view's bounds don't change, its -viewWillLayoutSubviews won't get called.

Since you say your setup is "frame-dependent" you have a couple options.

1. Do your setup in -viewWillAppear. Better than -viewDidAppear, and since you really need everything to be done after all the layout if completed but before it appears on screen, this is a legitimate reason why the method is there. If relevant, you could try calling -isMovingToParentViewController as described in Determining Why A View's Appearance Changed.

EDIT: -viewWillAppear is called before the view is added to the view hierarchy and therefor layout may not be completed. Sorry everyone, and thankfully with Swift UI we are moving away from procedural layout to declarative layout, and this is why.


  1. Call -layoutIfNeeded on the views you need to have the correct values where you need them to. Because this does the layout work it's expensive, and it could lead to repetitive work being done or even an infinite loop if triggered during its own layout process. But it's useful if it's needed to sync some values with Auto Layout so they can be used.

If this doesn't work let us know, it could have to do with the nature of the constraints themselves or something else. The Auto Layout iterations can be tricky to sync with. Good luck.

like image 79
Mike Sand Avatar answered Oct 24 '22 18:10

Mike Sand