Force Immediate Layout Update

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?

17 Likes

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.

65 Likes

Ah great, that’s exactly the sort of thing I was looking for. I’ll give it a shot. Thanks!

1 Like

Thanks a lot ! It also solved my issue :slight_smile:

Worked for me! It’s kinda stupid the method must be called “externally” isn’t it.

1 Like

thanks!

1 Like

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.

2 Likes

Hmmm…

LayoutRebuilder.ForceRebuildLayoutImmediate not work for me. Anyone also got this?
I used Dotween for animation to modifying LayoutElement.preferredHeight

3 Likes

Updated: I notice that changing LayoutElement.preferredHeight took more than one frames to rearrange to correct position.

Thats maybe why the LayoutRebuilder.ForceRebuildLayoutImmediate or Canvas.ForceRebuild wont work.

I’ll try workaround by changing size of RectTransform directly instead of changing LayoutElement.

However is this a bug or by design?

3 Likes

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.

3 Likes

Thanks for this idea ^^

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:

gameObject.SetActive(!gameObject.activeSelf);
gameObject.SetActive(!gameObject.activeSelf);

Why does toggling object repaints it on Canvas, where’s specific specialized methods to do it fail? Idk, Unity.

19 Likes

The same issue with layout groups… There is another solution to refresh it manually:

horizLayoutGroup.CalculateLayoutInputHorizontal();
horizLayoutGroup.CalculateLayoutInputVertical();
horizLayoutGroup.SetLayoutHorizontal();
horizLayoutGroup.SetLayoutVertical();
13 Likes

Did you find any solution?

Thanks! :slight_smile:

========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

layout.CalculateLayoutInputVertical();
layout.CalculateLayoutInputHorizontal();
layout.SetLayoutVertical();
layout.SetLayoutHorizontal();

========Original Answer========

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:

  1. check “Control child size width/height” on the layout group component of the parent object.
  2. delete content size fitter of any child object.

https://stackoverflow.com/questions/53059487/unity3d-parent-has-a-type-of-layout-group-error/58056555#58056555

31 Likes

For me moving UI elements’ updating into LateUpdate() + call to LayoutRebuilder.ForceRebuildLayoutImmediate in the end of it did the trick.

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…

3 Likes

Finally! Thank you!

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 :wink:
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.

Note 2: Minimize LayoutGroup nesting when possible. Layout rebuild is very expensive. More info on topic:
Unite Europe 2017 - Squeezing Unity: Tips for raising performance.

32 Likes

only this worked for me:

messageContainer.GetComponent<VerticalLayoutGroup>().enabled = false;
messageContainer.GetComponent<VerticalLayoutGroup>().enabled = true;
3 Likes

the right answer is gresolio’s

1 Like