Can't seem to make auto layout work right

So I have a Split View controller for my app.


The Master View Controller does not want to layout correctly in different sizes, so I figure I must be doing something wrong.


This is the layout, the way it shows in the iPhone 4s:


I very carefully set up stack views, as shown:


"Topmost" includes two items (Size of ... label, and the Help and Reset buttons) in a horizontal stack view, and the segmented controller.

"Roll Settings" includes three Vertical Stack Views (L-R: Labels, Selection settings, and Steppers).

"Toggles" includes two Horizontal Stack Views, which each includes a label and switch associated with the label.


As it stands, I set the following constraints:

Topmost Stack view: Leading & Trailing Space to container, Top Space to Top Layout Guide, and set all of those stupid "helpful" settings from 8 to "Standard" where applicable.

Roll Settings: Leading & Trailing Space to container, Top Space to Topmost Stack View, all set to "Standard."

Toggles: Leading & Trailing Space to container, Top Space to Roll Settings, all set to "Standard."

Roll with …: Align Center X to Superview, Top Space to Toggles, all set to "Standard."

Current Settings Display: Leading & Trailing Space to container, Bottom Space to Bottom Layout Guide

I then added a contstraint for "Roll with…" to be Bottom Space to Current Settings Display and set it to "Standard."


Layout is fine on iPhone 4s.

When I switch to the iPhone SE, the "Roll with…" button is centered, but shows a yellow dashed box:

Indicating it is in the wrong location. It is pretty clear to me that it wants to be up just a few pixels, but can't be because of conflicting constraints; however, removing either the Top Space or Bottom Space constraint will not build (generates errors with auto layout that cannot be resolved).

I think I get that it's torn between deciding which of the constraints to follow, but even if I change the priority to 750 or 250, nothing changes, which it should if the constraints confict, which they don't really seem to.

I should note that the button isn't the only 'yellow' item in the view, the Toggles Stack View and Current Settings text view are also yellow (but neither the Topmost nor the Roll Settings Stack Views are, they are both blue):

I tried removing one constraint, the Top Space of "Current Settings," which resulted in this:

… about which XCode generates an error saying that the "y" position of that text view is ambiguous (which sort of makes sense; even though I gave it leading/trailing edge information and the bottom space, it's unclear how tall it should be).

With these constraints, all of the Stack Views, the main Button, and the Text View are all blue, but then moving to another size class they will not line up correctly, and I think they probably should.

Ergo, I believe I must be misunderstanding the value, or use, of Stack Views, size classes, and constraints.

What am I missing?

Accepted Reply

That all sounds fine from what I can tell. It's quite possible you are running into Xcode bugs with stack views in IB. 😐 Does it lay out correctly without warnings on device / simulator? FWIW, the only time I've tried to use nested stack views (in a tvOS app a few months back) I did run into wacky IB behaviour like that. Constantly misplaced views in IB. After hitting "update frames" enough times in a row it would settle down with no warnings, until the next time I changed anything or reopened the file. It seemed to work OK on the device so I just chalked it up to typical Xcode bugs. My iOS apps all need earlier deployment targets so I haven't used any stack views on iOS recently.


Does your text view have the "scrollable" checkbox ticked? It shouldn't, if auto layout needs to calculate its vertical size based on its contents. If it is scrollable, then you probably need an explicit height constraint on it.

Replies

Also, for some reason the images aren't loading in the question area, which may make it harder to understand.

I didn't look at the detail ; but when you have conflicting constraints, change the priority level of a constraint may solve the problem for auto layout.

> I think I get that it's torn between deciding which of the constraints to follow


UIKit should never be put in that position. If that is true, you have multiple ambiguous / conflicting constraints and that's considered a programming error. That said, stack views are a relatively new API feature and have had their share of bugs; it's possible you're running into something like that. If you can distill it down to a simple test case and file a bug report on it that would help there.


Does every one of your stack views' subviews have an intrinsic content size or explicit size constraint? From your description (I agree it ***** that we can't use pictures, and it's even worse that the forum software trolls you by making it look like you can post them, then just silently doesn't display them) it sounds like you're sizing the stack views based on their contents, which is fine. But every subview needs to be able to calculate its own intrinsic content size. So for text views they must not be scrollable or must have an explicit height constraint, labels must be a fixed number of lines or have an explicit preferred layout width, etc.


Apple does have some auto layout debugging tips but in general it's a fairly annoying process 😝

Thanks, I did try changing the priority of the constraints, and it didn't help. I might have chosen the wrong ones to prioritize, however, so it may just take a lot more experiementing on my part to make it right.

Thanks. I did try having the constraints with different priorities, based on what I thought would make the most sense (for example, the priority for left/right edge and bottom space to container margins at 1000 for the bottom most item, but with a lower priority for the object above it—theoretically, the bottom text view should always hug the bottom of the view and stretch to each edge, but the button above it should maintain its position only after those first three constraints are met), but that didn't work. I am going through each item to make sure I did, indeed, make sure to set all of the sizes correctly.


I deliberately decided to design the base UI for the smallest form, with the assumption that it would make it easier to work when the screen sizes increase. Maybe (despite Apple's specific advise as promulgated in at least one of their WWDC Adaptivity sessions) I should reverse it? Form the base UI with the largest screen sizes and then find the problems one at a time as I go down?


Ultimately, I just think this whole Auto Layout thing is pretty freaking awful, although the struts & springs had some flaws it seemed to make a lot more sense. Maybe I'm dense.

> but with a lower priority for the object above it


Normally, if a view is to be allowed to resize, there should be no size constraint on it at all in that dimension, just required constraints on its edges. You shouldn't have to mess with priorities in most cases. Maybe an inequality if you only want to allow it to expand / shrink to a certain size but that's fairly unusual in my experience. A button that sticks to the bottom and stretches to the edges would just have four constraints - fixed space to leading & trailing container (or margin), fixed space to bottom layout guide, fixed space to one nearest neighbour above, all required (priority = 1000). The button's height would be determined by its intrinsic content size. (It also has an intrinsic width but it's fine to override the intrinsic size with explicit constraints such as the leading / trailing edges.) The view above would not have a height constraint if it's allowed to resize. It would be pinned to both the button on the bottom and some other view or layout guide that's stuck to the top. The required position of its edges will indirectly determine its height.


I do not share your opinion of auto layout, but there are plenty of people on these forums who do. 🙂 Probably small consolation when you're just trying to get it to work.


Maybe temporarily while you're figuring things out you could set some explicit sizes on some of your views just to see how it affects everything else as the screen resizes. Make sure to use all the debugging tools at your disposal (_autolayoutTrace, constraintsAffectingLayoutForAxis, the view debugger etc.)


Starting with the smallest screen is the right way to go IMO. It's much easier, design wise, to expand things to fill extra space, than it is to try and shoehorn a bigger design into a smaller space.

Okay. I clearly am missing something in my brain WRT how this should work.


The Topmost Stack View hugs (in each case, set to constant = 0 or standard, if available):

- Top Layout Guide

- Leading Space to Superview (which is the View)

- Trailing Space to Superview


The next Stack View (Roll Settings) hugs Leading & Trailing to superview, with a vertical to the Topmost Stack View


The next Stack View (Toggles) hugs Leading & Trailing to superview, with a vertical to Roll Settings


Next I set the bottom-most element, a Text View, to hug Leading & Trailing to superview, as well as Bottom Layout Guide


Finally, I set the button to Center Horizontally in Container, and gave it constraints to hug Vertical Spacing to the Toggles Stack View above it, and the Text View below it.


As I comprehend it, that *should* mean

- the Topmost Stack View should, always, touch the top layout guide and expand to fill left-to-right;

- the Roll Settings Stack View should, always, touch the Topmost Stack View and expand to fill left-to-right;

- the Toggles Stack View should, always, touch the Roll Settings Stack View and expand to fill left-to-right;

- the bottom Text View should, always, touch the bottom layout guide and expand to fill left-to-right;

- and the button should be centered left-to-right, and be a standard distance between the Text View below it, and the Toggles Stack View above it, with the Toggle Stack View vertical spacing priority set lower than the vertical spacing priority betwen the button and the Text View, so that it will preferentially hug the Text View below rather than the Toggle Stack View above.


It is entirely possible that this is completely wrong, because even with these constraints set, moving up the devices in the device bar misplaces multiple items every time. Sometimes, they're only off by a few pixels. However, even if they are misplaced, when I go back to the iPhone 4s in the Device Bar they are still misplaced, even though all I did was click the larger devices in the device bar and did not change their positions.

Just as a test, I checked again. (also, I should note that all items are set to Intrinsic Size of "Default (System Defined)".)


I have all of the auto-layout rules as noted above for the iPhone 4s. There are zero auto-layout issues to resolve.


I clicked the SE. The Roll Settings Stack View was misplaced; expected height = 115 actual height = 117. Also, the bottom text view was misplaced; expected y = 378 actual y = 380 and expected height = 182 actual height = 180.

No other items were misplaced, and those were the only two auto layout warnings (yellow triangle, not red stop sign).


Clicking on the iPhone 4s in the Device Bar, there were now three auto-layout issues:

Roll Settings Stack View expected height = 115 actual height = 117

Button expected y = 340 actual y = 342

Toggles Stack View expected y = 261 actual y = 263.


All of the actual constraint definitions are still exactly the same; that is, Top/Bottom/Leading/Trailing are still listed as "Superview" or "Default" to the correct other items.

Also, I greatly appreciate your patience. I'm extremely frustrated at the moment.

Feel your pain.


I believe that size classes should be your friend w/an SVC - perhaps this (dated?) Apple video on adaptive design can shed light: WWDC 2014 session 216


Sorry, can't help more, but glad to call someone for you if your head explodes, just leave an ICE contact. JP is your best bet on AL, so stick w/him and good luck.

I also noted that I failed to answer your question about the size constraints on the individual items in each Stack Class.


Topmost Stack View (a Vertical Stack View) has a Segmented Control, and a sub-Stack View ("Top Labels Stack View") whose members are one label with a line setting of 1 and a fixed font size of System 13 and two buttons (each of which has a font size of System 15). The Top Labels Stack View itself is Horizontal, with Equal Spacing for the distribution.


Roll Settings Stack View (a Horizontal Stack View) has three sub-Stack Views, each of them Vertical:

  • Selection Labels

    Which has four labels. Each label is set to Font System 17, Autoshrink to minimum font size of 9, 1 line, tighten letter spacing

  • User Selections

    Which has four labels, tied to the steppers in the next Stack view. They are also set to Font System 17, Autoshrink to minimum font size 12, 1 line

  • Steppers

    Which has four steppers, each tied to the User Selections labels in the middle.

Toggles Stack View (a Vertical Stack View) has two sub-Stack Views, each of them Horizontal:

  • Sorting

    Which has a label and a Switch, the label set to font size System 12, Autoshrink to minimum size 10, tighten letter spacing, and 1 line

  • Sounds

    Which also has a label and a switch, with identical settings as Sorting


As for the other two items, the main action button has a font size of System 15 and a bgcolor; the text view is font size of System 12 and it is scrollable, but is not in a Stack View (it sits in the parent view as do the button and the Stack Views).


Every item, across the board, is set to Intrinsic Size "Default (System Defined)" on the Size Inspector.

That all sounds fine from what I can tell. It's quite possible you are running into Xcode bugs with stack views in IB. 😐 Does it lay out correctly without warnings on device / simulator? FWIW, the only time I've tried to use nested stack views (in a tvOS app a few months back) I did run into wacky IB behaviour like that. Constantly misplaced views in IB. After hitting "update frames" enough times in a row it would settle down with no warnings, until the next time I changed anything or reopened the file. It seemed to work OK on the device so I just chalked it up to typical Xcode bugs. My iOS apps all need earlier deployment targets so I haven't used any stack views on iOS recently.


Does your text view have the "scrollable" checkbox ticked? It shouldn't, if auto layout needs to calculate its vertical size based on its contents. If it is scrollable, then you probably need an explicit height constraint on it.

Setting constraints for the height of the Text View (which I do want to be scrollable, especially at the smalles screen sizes) helped until I got to the iPhone 6s size. At least now, however, it's the two middle Stack Views that are misplacing each time, which certainly narrows down the problem. When I use the Device Bar to pick the 6s/6s+, the two middle Stack Views are getting badly misplaced (one of them, the Toggles Stack View, completely off-screen at y = +1292).


Now, what is happening is that the Topmost Stack View, the bottom Text View, and the main Button are all in the correct place each time, on all size classes. The Roll Settings Stack View immediately below Topmost is having its height sized up to huge numbers in the 6s/6s+. So my next step might be to constrain the size of that element? (That's what I'll try, and see what happens).

Okay, that (setting a constraint for the Roll Settings Stack View's size) worked on the 4s, SE, and 6s, but not the 6s+. Now, however, even though the Roll Settings Stack View is no longer hugging the Topmost Stack View, it is at least still visible in the scene, and would look weird but be usable on the device (at least, given what IB is showing).


In the simulator, it is displaying properly on all Phone devices (so far, I am still testing the smaller phones, but at least on the largest sizes it works fine) with these settings.


So, basically, the simulator is displaying them correctly, even if IB is not.

Went back and deleted the size constraint from the Roll Settings Stack View, and while IB is giving me complaints about the positioning of that item and the Toggles Stack View below it, the simulators are showing the elements in the correct places on all Phone sizes (though, I note that because it's XC8 ß, there is no 4s simulator). I can work on the iPad later, not as big a deal since it seems to be pretty close to where I want it to be.


So, what's the upshot?


1) I was expecting Auto Layout to be smarter than it is

2) I had a big-picture grasp on how it works, but not enough fine detail understanding to make it do what I needed it to

3) Text Views are cool, but they are also quite finicky

4) Stack Views work pretty well to alleviate some of the quirks of Auto Layout, but they appear to be a work in progress?


Anyway, thanks Junkpile, your feedback was a huge help. 🙂


Now, I just have to figure out why it won't let me specify a different layout for landscape mode, even with Vary By Traits. Small steps.