There is one thing that is really making me crazy about auto layout , I'm doing my tests and found that if you subclass a UIView and you put some views with their constraints is impossible to know the computed value.
Let't say that we have a TestViewClass that inherits from UIView
, we've got some subviews inside. we want to use it for iPhone and iPad, so we use in two sizes, let's suppose 100x100 and 200x200 we made our constraints to make everything work. We build this view placing those subviews in some positions.
Now we need to build another subviews that contains a number of buttons as subviews (contentView) which number is given at runtime.
The algorithm will pick the size of this content view and put the correct number of buttons calculating the space between them in a way that the will be at the same distance from each other but covering the whole content view width. For example, the contentView is 200 point in width, buttons are 3 and squared with a side of 60. Together they cover 180, so we have 20 points left that should be placed as a space between the buttons->10 points.
That's pretty easy, made thousand of times before auto layout. To do that I need the width of the contentView which has some constraints to its superview that make it resize its width according to superview size, the problem is that I can't find any place inside UIView implementation where I can get the final value of the size of the contentView.
I tried view -layoutSubviews
, -updateConstraints
, -didMoveToSuperview
, value show are alway the original one. When the new frames are calculated?
I clearly didn't get something about auto layout...
I discovered this problem trying to set table view cell height, trying to make them appear all on screen independently by the size of the table view. here is the related question other question
-
1Have you tried checking out the `frame` values in the view controller's `viewDidLayoutSubviews`? – Rob Jun 17 '13 at 16:52
-
Hi Rob, I didn't tried because they are private properties so their not visible.. but even if they are correct I need to catch them inside the subclass. – Andrea Jun 17 '13 at 18:07
2 Answers
Finally I've get what I was missing about auto layout and it is a fundamental concept.
Autolayout doesn't work like autoresizing masks, the new frames aren't updated instantaneously but by request.
This makes a huge difference, it seems to be in sync with the CATransaction render cycle, that makes sense, because layout calculation could be an expensive task and is useless to do until you really need it, most of the time during rendering.
Of course there are few exceptions, like this one and how can we force auto layout to make this calculation? we can do setting -setNeedsLayout
method and -layoutIfNeeded
. the first method marks the view as "dirty" and the second forces an immediate layout. If you set call those two methods in -didMoveToSuperview
right after you get correct updated frames.
Hope this helps,
Andrea
-
Thanks for the great explanation. Two years later this helped me fix the same problem in a different implementation. – miken.mkndev Dec 12 '15 at 12:27
You can use view controller's viewDidLayoutSubviews
, which will notify you when the views are laid out. If all of this is adding of constraints is happening in a custom UIView
, you could simply write a method for your view called viewDidLayoutSubviews
and have the view controller call that when the view controller receives it. At this point, the containers should have their dimensions properly configured.
But, there are a couple of approaches of even spacing a bunch of buttons without needing to bother knowing the size of the container view:
One approach I've used for even spacing controls in a container is to create what I call "spacer" views (views that are present, but just have a
clearColor
background, so you can't visually see them). You can then create a series of constraints using, effectively, something like:H:|[spacer1][button1(60)][spacer2(==spacer1)][button2(60)][spacer3(==spacer1)][button3(60)][spacer4(==spacer1)]|
If your number of buttons is fixed, you can do this in a single
constraintsWithVisualFormat
, like above. If it's a variable number of buttons, you can iterate through them, building a VFL string for each pair of button and spacer (e.g. the first button would use VFL ofH:|[spacer][button(60)]
all the subsequent ones would create another button and another spacer and use the following VFL:
H:[previousButton][spacer(==originalSpacer)][button(60)]
and then I add one final spacer at the end:
H:[lastButton][spacer(==originalSpacer)]|
This is a little cumbersome, but as you can see, your controls are perfectly laid out, evenly spaced, and you never needed to know the dimensions of the container.
Another approach is to use the
NSLayoutFormatAlignAllCenterX
, attribute. This bypasses the need to create all of those spacers, but if you use a simple algorithm like I do below, the centers will be evenly distributed amongst themselves. Thus, in addition to the standard vertical constraints and the horizontal width constraint for the button, you can then control the horizontal placement of the buttons with:[containerView addConstraint:[NSLayoutConstraint constraintWithItem:button[i] attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:containerView attribute:NSLayoutAttributeCenterX multiplier:2.0 * (double) (i + 0.5) / (double) n constant:0.0]];
where
i
is the zero-based index of which button you're setting this horizontal constraint for, andn
is the number of buttons.

- 415,655
- 72
- 787
- 1,044
-
You can also set your spacer views to hidden. Hidden views participate in layout. – rob mayoff Jun 18 '13 at 04:15
-
@robmayoff Agreed. Or set `alpha` to zero. A bunch of options there. Thanks! – Rob Jun 18 '13 at 04:19
-
1Hi @Rob thanks for your answer it's a valuable one, I up voted , but is not the point. I guess that I've figured out. Regards – Andrea Jun 18 '13 at 05:55