For awhile, a pretty frequent “gotcha” for me when working with Unity’s new UI system is that items added to a layout group (such as HorizontalLayoutGroup, VerticalLayoutGroup, etc) are not immediately positioned within the layout group.
This would frequently result in position and animation calculations being incorrect until I remembered “ahh yep, got to do this in a coroutine, yield a frame, and then do these calculations.” This wouldn’t be so bad, but it also results in an ugly single frame during gameplay where things are not positioned correctly - that yielded frame.
I’ve been searching for a way to force the layout groups to update immediately, so that I can perform layout-related calculations all at once and avoid the coroutine and ugly frame issues. Canvas.ForceUpdateCanvases seems to work, but is there a better way to do this? I would have thought that calling the ILayoutGroup functions to update layout would work, but they don’t do anything when I call them. Are there any better options, or is ForceUpdateCanvases the correct way to do it?
The following function LayoutRebuilder.ForceRebuildLayoutImmediate should prove useful. Instead of forcing a rebuild of all the Canvases which can have a significant performance impact, you can call this function on the root (top parent) object in the hierarchy the contains the layout component affecting the object in question.
It isnt stupid. If you change 1 thing and you have to rebuild the layout immediately, you pay the cost up front, even if you want to change 1000 things in one frame. In that scenario you would pay the rebuild cost 1000 times, instead of one.
Anytime you change a RectTransform (or any UI element that needs its quads redrawn) it sets a dirty flag on the layout its in. Sometime later (probably same frame, or the next), a single function call to update the layouts at the root is called if the layout is dirty - meaning if it even needs to be rebuilt.
I’m changing the spacing on a vertical layout group and ForceRebuildLayoutImmediate does not work for me either as well as MarkLayoutForRebuild. I had to turn off the gameobject, wait a frame, then turn it back on to get it to rebuild the layout.
Although, I was changing multiple UI components (text, buttons, images etc) in Editor.
The following did not had any effect on canvas repainting (Unity 2018.2.0f2):
Undo.RecordObject(...)
LayoutBuilder.ForceRebuildLayoutImmediate
Canvas.ForceUpdateCanvases
SceneView.RepaintAll()
...
Other set dirty methods
Only disabling / re-enabling the object did the trick. So I’ve made this dirty hack:
========Update=======
Today I encountered a similar problem, and I’ve found that my original answers just solved another problem that caused by this question, but it doesn’t solve this question. I’ll still leave the original answers here to help others.
Right now I still have to use the following ways: LayoutRebuilder.ForceRebuildLayoutImmediate or
I’ve been haunted by this problems for over 1 year, and tried all the ugly fixes including calling ForceUpdateCanvases or using a yielded frame etc. mentioned by @kromenak . Finally today I got it done in a proper way.
The solution is very simple:
check “Control child size width/height” on the layout group component of the parent object.
Obviously that required a bool flag to tell whether to update or not. Or I could have done it in a separate script, and then just disabled that to avoid calling LateUpdate needlessly…
I want to slightly expand the Sun-Pengfei’s answer. After long adventures and frustrations with UGUI, I can say with confidence that reading the source code helps better than official documentation. There are many “not a Bug, but rather a feature by design” things, won’t be fixed in current HorizontalOrVerticalLayoutGroup
To avoid using ForceRebuildLayoutImmediate or/and some manual update workarounds, the following approach works well:
Both “child controls size” checkboxes must be enabled and “child force expand” disabled for all ILayoutController (Horizontal, Vertical, also for nested ones), then to control the dimension of the objects you simply use the LayoutElement component (min, pref, flex sizes). As a result, any dynamic content is correctly aligned, regardless of the activation/deactivation/addition/removal of neighbors.
Note 1: There is no need for ContentSizeFitter component for GameObjects, that are first level children of any LayoutGroup. The main thing is to correctly set the checkboxes, as described above. However ContentSizeFitter may be useful for top level container, or children of children that are not directly controlled by any LayoutGroup.