Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Force Immediate Layout Update

Discussion in 'UGUI & TextMesh Pro' started by kromenak, Dec 8, 2015.

  1. kromenak

    kromenak

    Joined:
    Feb 9, 2011
    Posts:
    270
    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?
     
  2. Stephan-B

    Stephan-B

    Joined:
    Feb 23, 2011
    Posts:
    2,269
    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.
     
  3. kromenak

    kromenak

    Joined:
    Feb 9, 2011
    Posts:
    270
    Ah great, that's exactly the sort of thing I was looking for. I'll give it a shot. Thanks!
     
    david-wtf likes this.
  4. Olivier356

    Olivier356

    Joined:
    Sep 14, 2015
    Posts:
    12
    Thanks a lot ! It also solved my issue :)
     
  5. Verfin

    Verfin

    Joined:
    Jun 2, 2013
    Posts:
    5
    Worked for me! It's kinda stupid the method must be called "externally" isn't it.
     
    Deleted User likes this.
  6. Deleted User

    Deleted User

    Guest

    thanks!
     
    srdjans likes this.
  7. KGC

    KGC

    Joined:
    Oct 2, 2014
    Posts:
    12
    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.
     
    trombonaut and TextusGames like this.
  8. benzsuankularb

    benzsuankularb

    Joined:
    Apr 10, 2013
    Posts:
    132
    Last edited: Feb 24, 2018
  9. benzsuankularb

    benzsuankularb

    Joined:
    Apr 10, 2013
    Posts:
    132
    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?
     
  10. dustin_red

    dustin_red

    Joined:
    Feb 7, 2018
    Posts:
    46
    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.
     
    Ultroman, sjk000 and xVergilx like this.
  11. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    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):

    Code (CSharp):
    1. Undo.RecordObject(...)
    2. LayoutBuilder.ForceRebuildLayoutImmediate
    3. Canvas.ForceUpdateCanvases
    4. SceneView.RepaintAll()
    5. ...
    6. Other set dirty methods
    Only disabling / re-enabling the object did the trick. So I've made this dirty hack:
    Code (CSharp):
    1. gameObject.SetActive(!gameObject.activeSelf);
    2. gameObject.SetActive(!gameObject.activeSelf);
    Why does toggling object repaints it on Canvas, where's specific specialized methods to do it fail? Idk, Unity.
     
  12. gresolio

    gresolio

    Joined:
    Jan 5, 2014
    Posts:
    17
    The same issue with layout groups... There is another solution to refresh it manually:
    Code (CSharp):
    1. horizLayoutGroup.CalculateLayoutInputHorizontal();
    2. horizLayoutGroup.CalculateLayoutInputVertical();
    3. horizLayoutGroup.SetLayoutHorizontal();
    4. horizLayoutGroup.SetLayoutVertical();
     
  13. dilippatil

    dilippatil

    Joined:
    Jan 4, 2015
    Posts:
    9
    Did you find any solution?
     
  14. Iustitio90

    Iustitio90

    Joined:
    Jul 25, 2019
    Posts:
    1
    Thanks! :)
     
  15. Sun-Pengfei

    Sun-Pengfei

    Joined:
    Nov 12, 2015
    Posts:
    37
    ========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:
    Code (CSharp):
    1. LayoutRebuilder.ForceRebuildLayoutImmediate
    or
    Code (CSharp):
    1. layout.CalculateLayoutInputVertical();
    2. layout.CalculateLayoutInputHorizontal();
    3. layout.SetLayoutVertical();
    4. 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...-type-of-layout-group-error/58056555#58056555>
     
    Last edited: Mar 15, 2022
  16. perziili

    perziili

    Joined:
    Apr 24, 2018
    Posts:
    6
    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....
     
    xNex, jfaraday1879 and pavel_in like this.
  17. unnanego

    unnanego

    Joined:
    May 8, 2018
    Posts:
    195
    Finally! Thank you!
     
  18. gresolio

    gresolio

    Joined:
    Jan 5, 2014
    Posts:
    17
    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.

    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.
     
    Last edited: Oct 15, 2020
    c-Row, Volgix, okanpasa and 29 others like this.
  19. CubeGameStudio

    CubeGameStudio

    Joined:
    Dec 1, 2019
    Posts:
    18
    only this worked for me:


    Code (CSharp):
    1. messageContainer.GetComponent<VerticalLayoutGroup>().enabled = false;
    2. messageContainer.GetComponent<VerticalLayoutGroup>().enabled = true;
     
    Powzone and Missile_ like this.
  20. unnanego

    unnanego

    Joined:
    May 8, 2018
    Posts:
    195
    the right answer is gresolio's
     
    Powzone likes this.
  21. mllhild

    mllhild

    Joined:
    Apr 30, 2018
    Posts:
    3
    works for me :D
     
  22. mgear

    mgear

    Joined:
    Aug 3, 2010
    Posts:
    9,349
    ^ for me that one didnt work on 2018.3.x..

    this one did,
     
    mdrunk likes this.
  23. SergeantBiscuits

    SergeantBiscuits

    Joined:
    Jul 22, 2012
    Posts:
    17
    It all makes sense. If you're still having issues, you're building and managing your layouts improperly.

    Re: Stephan_B's solution w/ LayoutBuilder, if it's not working for anyone, make sure that you pass in an object with a layout component attached to it. Targeting an object higher up in the hierarchy but without a layout component doesn't work.
     
    Last edited: Jul 15, 2020
    LazloBonin likes this.
  24. Discipol

    Discipol

    Joined:
    May 6, 2015
    Posts:
    83
    Hi! I tried your script and it does not do anything. I have VerticalLayout and ContentSizeFitter with prefferedHeight in 3-4 levels. I have tried putting your script everywhere but nothing works. The intent was to prevent my tooltip from flickering while it autoresizes and because its size changes, have it unaligned when it finishes autoresizing.

    Would be cool if you merge the base ContentSizeFitter with your CustomSizeFitter and have a single solution
     
  25. Discipol

    Discipol

    Joined:
    May 6, 2015
    Posts:
    83
    I somehow got it working in Play mode, then trying to set it for real it doesn't work anymore.
    Then I half-got it working afterwards. I wish it would behave like ContentSizeFitter in editor mode. Now all my hierarchy looks overlapped and messy even though it sort of works in Play mode. I am sure I didn't remove a ContentSizeFitter somewhere.

    If you are looking at an upgrade, then overriding the original ContentSizeFitter's functionality of vertical autosizing (in edit time) would be a great boost!

    What license are you releasing this under?
     
  26. Faiqlaiq

    Faiqlaiq

    Joined:
    Nov 27, 2017
    Posts:
    6
    This is correct answer for me :D
     
  27. RendergonPolygons

    RendergonPolygons

    Joined:
    Oct 9, 2019
    Posts:
    98
    this worked for me though applied to a GridLayoutGroup, thanks a bunch :)
     
  28. pavel_in

    pavel_in

    Joined:
    Jun 18, 2017
    Posts:
    5
  29. LazloBonin

    LazloBonin

    Joined:
    Mar 6, 2015
    Posts:
    809
    Just to chime in that this was the fix for me.

    LayoutRebuilder.ForceRebuildLayoutImmediate
    is misleading. Its first parameter is called
    layoutRoot
    , but in fact, you must call it on each LayoutGroup, not on a root object that contains LayoutGroups.

    So I created this utility method instead and it refreshes, in the same frame:

    Code (CSharp):
    1. public static void RefreshLayoutGroupsImmediateAndRecursive(GameObject root)
    2. {
    3.     foreach (var layoutGroup in root.GetComponentsInChildren<LayoutGroup>())
    4.     {
    5.         LayoutRebuilder.ForceRebuildLayoutImmediate(layoutGroup.GetComponent<RectTransform>());
    6.     }
    7. }
    8.  
    This is false and should probably be fixed in the documentation.
     
  30. unnanego

    unnanego

    Joined:
    May 8, 2018
    Posts:
    195
    I'd replace Get Component with an array or something
     
  31. PavolP

    PavolP

    Joined:
    Nov 19, 2017
    Posts:
    1
    Thanks, this solved the issue for me:
    I was using a horizontal layout group to have even spacing between neighbouring rect transforms. When one of the rect transforms' width was modifyed via code, the layout group did not adjust accordingly to keep the spacing even.
     
    Xriuk likes this.
  32. Xriuk

    Xriuk

    Joined:
    Aug 10, 2018
    Posts:
    21
    Same here, I guess it should adjust its width and spacing automatically but it's not happening, what's the point of layouts if they do not update automatically?
     
  33. look001

    look001

    Joined:
    Mar 23, 2017
    Posts:
    111
    They usually rebuild automatically. However of you are using nested contentfitters you might get the problem that the parent fitter updates before the child (I made an example in this post)
     
    Celezt and Xriuk like this.
  34. cihad_unity

    cihad_unity

    Joined:
    Dec 27, 2019
    Posts:
    35
    I can't find LayoutBuilder in neither unity documentation nor import in my project.
    What is the current API for forcing relayout now?
     
  35. sandolkakos

    sandolkakos

    Joined:
    Jun 3, 2009
    Posts:
    282
    it lives in that namespace: `UnityEngine.UI` (I'm using Unity 2019.4.16f1)
    Try it liket that:
    Code (CSharp):
    1. UnityEngine.UI.LayoutRebuilder.ForceRebuildLayoutImmediate(rootRectTransform);
     
  36. cihad_unity

    cihad_unity

    Joined:
    Dec 27, 2019
    Posts:
    35
    Thanks, I've already used Canvas.ForceUpdateCanvases(). The drawback of it is, it updates the whole canvasses and rectransforms inside them which is a big perfomance drop.
     
    scorp2007 and sandolkakos like this.
  37. unity_VvCODKknmBJQGw

    unity_VvCODKknmBJQGw

    Joined:
    Aug 13, 2020
    Posts:
    7
    awesome solution. Thank you!
     
  38. HyperionSniper

    HyperionSniper

    Joined:
    Jun 18, 2017
    Posts:
    30
    Instead of grabbing the layout and forcing that to update, you could just steal a line from LayoutElement (which never seems to have any problem like this) - LayoutRebuilder.MarkLayoutForRebuild(transform as RectTransform) which achieves the same effect and doesn't require you to find or store anything extra, as people have done above.

    Seemed to work for me with a custom component which implements ILayoutElement that wasn't updating before. Don't know why nobody mentioned this, given that LayoutRebuilder.ForceRebuildLayoutImmediate was mentioned.
     
  39. Anonymous_Throwaway

    Anonymous_Throwaway

    Joined:
    May 9, 2020
    Posts:
    2
    Chiming in to the discussion above. After grappling with the answers above, I THINK that I've finally figured out why my code was working with some of the solution above, and it wasn't for others.

    Here are my basic understanding of this topic
    1. The UI items are only rendered in the frame, therefore, in order to get the most accurate layout size, any calculation can only be done AFTER the relevant child objects have been added to the layout group.
    2. This also means that the gameobject containing the VerticalLayoutGroup MUST BE ACTIVE IN THE SCENE before calling ForceRebuildLayoutImmediate, and waiting for the end of frame.

    For testing, since my code was already working, I did a test and found out point 2, which is the point that I think many people MIGHT be missing.

    levelSelectContent contains the VerticalLayoutGroup, while menuPanelObject[2] is the root gameobject. As you can see in my code, I simply enabled it, and it was able to get the correct size delta for me.
    Code (CSharp):
    1.      
    2.         var rectTransform = levelSelectContent.GetComponent<RectTransform>();
    3.         LayoutRebuilder.ForceRebuildLayoutImmediate(rectTransform);
    4.         yield return new WaitForFixedUpdate();
    5.         var anchoredPosition = rectTransform.sizeDelta;
    6.         Debug.Log("SizeDelta: " + rectTransform.sizeDelta);
    7.         menuPanelObject[2].SetActive(true);
    8.         menuPanelObject[2].GetComponent<CanvasGroup>().alpha = 0.0f;
    9.         LayoutRebuilder.ForceRebuildLayoutImmediate(rectTransform);
    10.         yield return new WaitForFixedUpdate();
    11.         Debug.Log("SizeDelta: " + rectTransform.sizeDelta);
    12.  
    upload_2021-9-26_14-9-36.png
     

    Attached Files:

  40. kenmountains

    kenmountains

    Joined:
    Jun 17, 2018
    Posts:
    1
    This works for me... but only if the canvas is active in the hierarchy when the scene is opened. I'm not sure if anyone else sets up their scenes that way (I have multiple canvases in a scene, and while I'm working on one I deactivate the others). I guess there's some sort of initialisation process run on canvases or on LayoutGroup when a scene is opened, that isn't being run if it's inactive?
     
  41. Xtro

    Xtro

    Joined:
    Apr 17, 2013
    Posts:
    604
    This is how I got my layout logic working properly.

    Code (CSharp):
    1.            
    2.             // We have to call this twice for the text and everything to be taken into layout calculation.
    3.             LayoutRebuilder.ForceRebuildLayoutImmediate(LayoutPanel);
    4.             LayoutRebuilder.ForceRebuildLayoutImmediate(LayoutPanel);
    5.  
     
  42. unnanego

    unnanego

    Joined:
    May 8, 2018
    Posts:
    195
    What do I do if I'm not using layout groups? I'm loading text from the storage, so UI elements should resize on the same frame, but it doesn't.
     
  43. konsnos

    konsnos

    Joined:
    Feb 13, 2012
    Posts:
    121
    Didn't try @Stephan-B 's solution as I couldn't find an easy way to retrieve root RectTransform.

    What I did is
    • Check Control child size
      • Add LayoutElement to children
    • Add Content Size Fitter
    Works as is. No code is required.
    Unity 2021.1.12
     
    Joaquin-VtLab likes this.
  44. sellverful

    sellverful

    Joined:
    Dec 4, 2018
    Posts:
    3
    This is just great!
    All other answers are hacks, but this answer is how you do it the correct way.
    Many thanks!
     
    Nocheatin likes this.
  45. MrMumbles420

    MrMumbles420

    Joined:
    Jan 2, 2019
    Posts:
    1
    After trying layout elements/content fitters, waiting for a frame, turning layoutgroups/contentfitters on and off. My final solution ended up being turning on the menu GameObject, turning correct buttons on, forcerebuildlayout, turn off menu. and turn menu back on where its wanted
    Code (CSharp):
    1.  
    2.     public enum MenuType
    3.     {
    4.         OnlineFriend,
    5.         OfflineFriend,
    6.     }
    7.  
    8.     public void OpenMenu(MenuType menu)
    9.     {
    10.         popUpMenu.SetActive(true);
    11.  
    12.         switch (menu)
    13.         {
    14.             case MenuType.OnlineFriend:
    15.                 buttonGameObject.SetActive(true);
    16.                 buttonGameObject.SetActive(true);
    17.                 break;
    18.             case MenuType.OfflineFriend:
    19.                 buttonGameObject.SetActive(false);
    20.                 buttonGameObject.SetActive(false);
    21.                 break;
    22.         }
    23.         LayoutRebuilder.ForceRebuildLayoutImmediate(popUpMenu.GetComponent<RectTransform>());
    24.         popUpMenu.SetActive(false);
    25.     }
    26.     //Then do something like.
    27.     Class.OpenMenu(Class.MenuType.OnlineFriend);
    28.     FunctionToSetMenuPosition();
    29.  
    The FunctionToSetMenuPosition will have the updated layoutgroup size at this point
     
  46. jantjedetweede

    jantjedetweede

    Joined:
    Oct 21, 2021
    Posts:
    7
    If you are destroying a child gameobject of the layout and you want to update in the same frame, make sure to call DestroyImmediate instead of Destroy, that was the issue for me.
     
    MattDavis, ratking and look001 like this.
  47. Nocheatin

    Nocheatin

    Joined:
    Jan 22, 2016
    Posts:
    4
    Agreed, applied this and instantly fixed my problem.
     
  48. AronTD

    AronTD

    Joined:
    Aug 31, 2013
    Posts:
    22

    This is the only thing that worked for me.
     
    justindpnt likes this.
  49. zxc76_die

    zxc76_die

    Joined:
    Apr 10, 2018
    Posts:
    9
    Using the pack from "CalculateLayoutInputHorizontal" is a good option. It works!
    But it doesn't always work for me.
    Here's if you do it with a delay, then it's perfect. That's why I packaged the solution this way.



    Use:
    Code (CSharp):
    1.     StartCoroutine(GetComponent<VerticalLayoutGroup>().ChangeUpdate());

    Write it down somewhere:
    Code (CSharp):
    1.  
    2.     public static class LayoutHelper
    3.     {
    4.        public static IEnumerator ChangeUpdate(this VerticalLayoutGroup horizLayoutGroup)
    5.         {
    6.  
    7.              //horizLayoutGroup.enabled = false;
    8.             yield return new WaitForEndOfFrame();
    9.             // horizLayoutGroup.enabled = true;
    10.             horizLayoutGroup.CalculateLayoutInputHorizontal();
    11.             horizLayoutGroup.CalculateLayoutInputVertical();
    12.             horizLayoutGroup.SetLayoutHorizontal();
    13.             horizLayoutGroup.SetLayoutVertical();
    14.             yield return null;
    15.         }
    16.     }
     
    look001 likes this.
  50. jfaraday1879

    jfaraday1879

    Joined:
    Dec 21, 2021
    Posts:
    3
    Oh my gosh, you just saved me and made my code so much cleaner. THANK YOU!!!!