UGUI Layouts and Fitters
Unity's first retained mode UI library, UGUI has been around for about a decade now, and the parts that have always confused new users the most are the Layout Groups and the Fitters. This article isn't going to go into the basics of what these elements are, and what they're for, because that is thoroughly covered in Unity's AutoLayout documentation. Instead, we're going to talk about the things that are confusing about them, and look at a couple of complex use-cases that people struggle with.
The Control Flags
Horizontal and Vertical Layout Groups have this block of flags, and most new users have the same question: What are these, and why do they always do the wrong thing? It's not an unusual experience to spend a large amount of time fighting with these, and not getting what you want. Worse, without a mental model of what's going on, a person may click boxes randomly, stumble on something that looks like a solution, only to find out later that it falls apart with a different set of data. So, let's talk about what these settings do, and when to use them.
Let's start with a Horizontal Layout Group, containing buttons, with all of these checkboxes turned off.
Each button is laid out in the container, according to the Child Alignment, with the Spacing value used to space them out. Their size is not driven, and can be changed arbitrarily.
Here, I've changed the width of the first one to 240, and the layout group has moved the other children over, to fit the wider element.
Let's start with the Expand settings. What do you think is going to happen if we click on Child Force Expand > Width? It's going to expand the width of those buttons, right? No, it's not. It's going to do this:
It has expanded the space between the items. Now, this is actually very useful, in some situations. This way, you can distribute spacing nicely, without having to manually calculate the gap size.
But it wasn't what we expected. Why?
The reason is that the button size is not driven. That is, we have not given the layout group permission to change the size of the buttons automatically.
To do this, we must turn on that Control Child Size checkbox.
Now our buttons have expanded to fill the available space, while maintaining the Spacing and Padding set on the layout group.
You can see that the Width of the buttons is now grayed out. That indicates that those values are now driven by the layout group, and cannot be changed manually.
What do you think will happen if we turn off Child Force Expand now? It's once again going to do something that is initially surprising.
The buttons are now being mashed to a Width of 14, and you can't change it.
This is the point where a lot of folks panic. What is going on? How do I fix this?
When Control Child Size is on, the Layout Group will set the size of each child element to the Preferred Size of that element (if there is room for it; otherwise, it will use the Min Size). For an element with no Layout Element on it, this size will be zero, so it will collapse entirely. In this case, the Button object has an Image component on it, which does implement ILayoutElement
. However, these buttons are using a 9-Sliced image. In the case of 9-Sliced images, the Preferred Size of the Image is the Min Size, and the Min Size is the border area of the 9-Slied image.
In this case, that size comes to 14, and that's why the Layout Group set the width to 14.
If we don't want our buttons to be mashed like this, there are a couple of ways we can fix this. We could add a layout group to the button root. This is a good option if you wish to fit the button to the contents, like a text button that fits to the string inside of it.
Alternatively, if we want fixed-width buttons, we could put a Layout Element component on each button, and assign an explicit width, but the end result of this is the same as what we had when we had all of the checkboxes turned off, and it doesn't afford us an advantage over that, in this situation.
Layout Group Settings: Cheat Sheet
I've created a free cheat sheet you can use as a quick reference, covering the things we've covered above.
Fitting a Container to its Children
Two of the modes we've discussed above can be used for fitted content.
Going back to our fixed-width button case, with the checkboxes all turned off, we can add a Content Size Fitter, and set Horizontal Fit to Preferred Size.
Likewise, in our fitted-button case, we can add a Content Size Fitter, and it accomplishes the same thing, with fitted buttons.
Fitting Nested Containers
Those cases were easy, but what about nested layout groups?
In this example, the button group is fitting its internal buttons horizontally, and the outer group is fitting the buttons and text vertically.
How did we make this work?
The Button Container is set up exactly the same way we set up the fixed-width buttons with the Content Size Fitter.
However, there is one key difference: There is no Content Size Fitter on the Button Group. This is OK, because we have another way to accomplish the same outcome.
On the Outer Container, we turn on Control Child Size > Width. This will cause each of its child elements, including the Button Container, to adopt their Preferred Size, which is the same thing the Content Size Fitter would have done. So, in the case of the Button Container, it shrinks to fit the buttons, even without a Content Size Fitter present.
We also add a Content Size Fitter to the Outer Container. This will allow the Outer Container to grow or shrink to fit our content. However, we also need to turn on Control Child Size > Height, because otherwise, the Text Block will not adopt its Preferred Size, and will not resize to fit its contents.
Troubleshooting Your Fitting
I know, some of you are thinking, "But why is it always broken when I try to do it?" So, I'm going to talk about some of the things that go wrong.
1.) Putting Your Content Size Fitter in the Wrong Place
First, a lot of people try to put Content Size Fitters in the wrong place, because they changed their mind about their layout, or because they didn't know which settings they needed to use to make a nested Layout Group fit its contents properly. This is flailing around, and we should not do that.
When you put the Content Size Fitter on a child of a Layout Group, you'll see a warning that looks like this:
This is not just an annoyance that you can safely ignore. It's also not Unity being mean and not letting you do what you want to do. Read the warning, and then remove that Content Size Fitter, because it does not belong there.
A Content Size Fitter can go onto any element that is anchored independently of any Layout Group, either because it is not in one, or because it is ignoring the Layout Group it is in (by means of a Layout Element component with Ignore Layout turned on, usually).
Examples of places you might effectively use a Content Size Fitter:
- The root of a dialog box.
- The root of a HUD footer.
- A button anchored to the corner of a window.
- The Content panel inside of a ScrollRect.
2.) Breaking the Chain
The most common mistake I see has to do with text in containers. People start with a layout like this:
And they want it to look like this:
But instead, they end up with this:
"But I am using a Content Size Fitter! Why is it not fitting?"
The usual cause of this problem is that the Vertical Layout Group on the root panel doesn't know anything about the Preferred Size of the text that is inside of the Text Block Container. If the Text Block Container doesn't have a Layout Group on it, we have broken the chain of sizing information. Each tier of the hierarchy must communicate sizing information, or this kind of collapse will happen.
So, let's go to the Text Block Container, and add one of these:
Now that we've added the layout group to the Text Block Container, the Vertical Layout Group knows what the preferred size of the box is, and it can lay everything out correctly.
3.) Trying to Fit Two Axes in a Content Size Fitter
This is another one I run into sometimes:
Unity's Content Size Fitter can only size to one axis. Do not try to use it for two. The behavior is undefined, and probably not what you want.
If you want a Content Size Fitter that works for two axes, I have a script for that. The Multi-Axis Content Size Fitter grows in one direction, up to a maximum, and then grows in the other direction. This is especially useful for alert dialogs that you wish to be narrower, when there is only a small amount of text.
Laying out a Dialog
Our objective is to make a dialog box that is similar to the above, but it needs to adapt well to a variety of image aspect ratios and text lengths. The dialog should grow taller when there's more text, without mashing the buttons or pushing them out of the dialog. It should also center the image vertically in the space to the left.
The classic rookie mistake most folks make right away is using too many Layout Groups for this layout. Layout Groups are expensive, and you should only use them where they're needed. If you find that they're useful for laying out static elements at design-time, that's fine: You can use a Layout Group at design-time, and then disable it when you're done using it.
In this case, the text will change size, so we will need one Layout Group to keep track of this, and convey Preferred Size information to the Content Size Fitter on the root of the Dialog.
But first, let's add the elements that won't be controlled by the Layout Group. We should do this by making proper use of the anchoring system, and add Layout Elements to ignore the layout.
Let's start by adding an Empty object to our Canvas, and sizing it 600 x 400.
Then we're gong to add two images: Background and Border.
We set the Border to stretch to the full size of the Dialog.
However, we've slightly oversized the Background, because the one we're using has an outer shadow.
Now we have a nice, empty panel to work with.
Now we'll add our header to the top, and fill it in.
Then, we add the buttons at the bottom.
These are anchored to the bottom of the dialog, either to the corners or the center. I like to keep the pivot centered, so that we can scale them up on mouseover, if we want.
Our layout so far:
Now we're gong to add our image. We should anchor it to the center left.
We've made it taller than wide, so that if we have some narrow aspect-ratio images, they can fit into the space without creating a huge gap with the text, because the Preserve Aspect setting likes to align narrow images to the left of the space they're in.
Speaking of which, be sure to turn on "Preserve Aspect" on the Image.
Now, this is very important: Add a Layout Element component to the Background, Border, Header, Cancel Button, and OK Button. You can do this quickly by selecting all of them, and then in the inspector, selecting Add Component > Layout Element. Then, turn on the Ignore Layout checkbox.
Now we can safely add our Vertical Layout Group to the Dialog Box root without causing everything to implode. We need to set the padding so that we aren't overlapping our content with the Header, the buttons, or the image on the left. We also want to set Control Child Size > Height on, so that the dialog box will set the Text Block to its Preferred Size.
Now we can add our Text Block to the Dialog. The Width is not going to change, so just set it to 370.
Now all we're missing is the Content Size Fitter on the Dialog Box root.
And there it is!
Now we can fiddle around with different images and text blocks, to see how well it holds up.
We've still got a bit of a problem when we use a short text block with a narrow aspect-ratio image.
We could shorten the height of the Image area, but let's do something a bit different:
First, let's set the Image Pos Y to 10, so that it's centered a little better between the footer buttons and the Header.
Next, we'll set a minimum height on our text block. To do this, go to the Text Block, and add a Layout Element component. Then, set Min Height to 210.
Now we have this result, which looks much better.
If we want to, we can also set the vertical alignment on the text to centered:
And there we are. We managed to create a very dynamic layout with just one Layout Group.
Laying out a Table
If you're laying out this table, you might think, "I'm going to make the text areas stretchy."
Do not be tempted to do this.
Each row lays itself out independently, so if you allow flexible sizing on any of the fields, your columns will fall out of alignment. This path will lead you to nothing but frustration.
Don't even bother using Control Child Size on your rows. Just set the columns to the size they should be, and everything will be nice and orderly.
However, sometimes, you really must have dynamically resizing columns. In that case, you should organize your table in columns, instead of rows. Then, all of the members of a column can resize together.
Here, we've laid out our data in a Horizontal Layout Group, containing columns. It's important, in this case, to provide a fixed height for each field, so that the rows all align.
It doesn't look much different from the table we made with fixed field sizes, does it? Where things really get interesting is when we start plugging some longer strings in here. Let's widen the title of our Pirate.
Now you can see that our Title column has widened to fit in the longer string. However, if we go back to our row-based layout with the fixed width fields, it also holds up pretty well, in response to this change:
So, depending on your needs, the column-based layout may be more trouble than it's worth.
Use Child Scale
There was one last setting on the Layout Groups that we forgot to cover. That is Use Child Scale.
I have never even used this, in practice, because it was added relatively recently. Also, it's not useful in the one way I would most like to use it, because it does not respond to Scale animations. So, you can't, for instance, use it to make a footer icon dock with a magnifier mouseover that scoots the neighboring buttons to the sides.
All it does is turn this:
Into this:
But not with scale animations. This may or may not be useful to you. It's mostly not relevant to the rest of this guide.
Got More Questions?
Subscribers can comment. Ask away! I might even write a new article to discuss your question.
Asset Credits
The UI borders, fills, and icons are all part of Hidden Achievement's internal stock UI sprite collection. I made them.
The sample art images all came from the Public Domain Image Archive, which is full of wonderful things.
Like this article? Want to see more like it?