Search Unity

  1. Looking for a job or to hire someone for a project? Check out the re-opened job forums.
    Dismiss Notice
  2. Unity 2020 LTS & Unity 2021.1 have been released.
    Dismiss Notice
  3. Good news ✨ We have more Unite Now videos available for you to watch on-demand! Come check them out and ask our experts any questions!
    Dismiss Notice

Content size fitter refresh problem

Discussion in 'UGUI & TextMesh Pro' started by drHogan, Oct 3, 2017.

  1. drHogan

    drHogan

    Joined:
    Sep 26, 2012
    Posts:
    188
    Hi guys,

    I am fixing an old tooltip system of mine, and I am going nuts on an apparently fairly simple issue. What it should to, is basically that the tooltip size should adapt to the text size. (the movement, not going out of screen, etc etc is all implemented and working)

    This is my tooltip



    The root is an empty object with the script, going down one we find the "container" that has a canvas, a canvas group and moves around (i could probably skip this one and bring it to the root) :



    Then ContentLayout is the one with Content Size Fitter and Horizontal Layout :



    And Tooltip Text is a simple TextMeshPro UI element of text.

    Now, the problem is, the whole system works, but not as it should. I.e. if I move my mouse pointer over an element, i get a tooltip with correct text, but resized according to the previous element hovered (so if I hover and leave and hover again a button, i get the correct size for that button because of having hovered it twice).

    This is a sample of what happens for example (the tooltip before was smaller)



    I guess the problem is that I have no idea on how to force an immediate refresh of the content size fitter. I tried deactivating it and re-activating it, i tried to wait for the end of frame with a co-routine before making it visible, etc etc. But the problem remains.

    Any hints on what i might tackle to solve it?

    Cheers,
    H
     
    Tymianek and gustavoluciogd like this.
  2. FunRobDev

    FunRobDev

    Joined:
    Jun 3, 2017
    Posts:
    9
    Hi there. I had a very similar setup for tooltips with the same issue. This solved my problem:

    Code (CSharp):
    1. tooltTipText = "new tooltip \n second row";
    2. Canvas.ForceUpdateCanvases();  // *
    3. tooltTipText.transform.parent.GetComponent<HorizontalLayoutGroup>().enabled = false; // **
    4. tooltTipText.transform.parent.GetComponent<HorizontalLayoutGroup>().enabled = true;
    * According to documentation, a canvas (which controls your UI elements) performs its layout and content generation calculations at the end of a frame, just before rendering, in order to ensure that it's based on all the latest changes that may have happened during that frame. Code that relies on up-to-date layout or content can call Canvas.ForceUpdateCanvasaes() to ensure it before executing code that relies on it.

    ** Sometimes the reenabling of the HorizontalLayoutGroup or VercticalLayoutGroup is also needed, but it depends on your configuration.

    Cheers :)
     
    Last edited: Apr 3, 2018
  3. ramkaentertainment

    ramkaentertainment

    Joined:
    Sep 23, 2018
    Posts:
    2
    Hello,

    I had created a start menu for my VR game using vertical and horizontal layout group functionalities and the content size fitter component to arrange the buttons automatically.

    After my Unity update from version 2018.2.8f1 to version 2018.3.0f2, my box collider of the buttons didn't work anymore. I calculate the box collider sizes via script in the start method, depending on the rect transform of the according buttons.
    I found out, that the box collider sizes were zero.

    With Unity 2018.2.8f1 the content size fitter has calculated the rect transform before the start method is called.
    With Unity 2018.3.0f2 the content size fitter calculates the rect transform obviously after the start method is called.

    Calling Canvas.ForceUpdateCanvasaes() before I calculate the size of the box collider in the start method worked for me too.

    Thanks!
     
  4. Cloppi

    Cloppi

    Joined:
    Sep 16, 2015
    Posts:
    2
    @FunRobDev ,
    Thank you! Your approach with additional re-enabling of HorizontalLayoutGroup component helped me to solve the problem as well. I've got an array of similar UI elements with dependence to Text.text.Lenght and they didn't want to placing correctly in the parent panel, but re-enabling replaced them right way.

    Thanks!
     
  5. doctorpangloss

    doctorpangloss

    Joined:
    Feb 20, 2013
    Posts:
    254
    This is a little wonky, you probably don't want to just disable/enable/ForceUpdateCanvases because it'll give UGUI people a heart attack. Implement `ILayoutElement` on your tooltip script, and in your CalculateLayoutInputHorizontal implementation, call CalculateLayoutInputHorizontal on the child. Also, implement preferredWidth using the child text's preferredWidth. Attach a content size fitter. That's it.
     
    unity_KNMBKAJDXv98xw likes this.
  6. dadude123

    dadude123

    Joined:
    Feb 26, 2014
    Posts:
    790
    Canvas.ForceUpdateCanvasaes()

    costs a ton of cpu cycles for no reason.
    Use LayoutRebuilder.ForceRebuildLayoutImmediate instead.
    You can pass in the transform for which you want to do the refresh.

    When using it with content size fitter you might want to call it twice with the same object in some situations (when you have a contentsizefitter + layout control at the same time).

    Calling that method twice will still be 100x faster than forcing a full refresh of the whole canvas.
     
    zaktan, ltomov, fleity and 8 others like this.
  7. losingisfun

    losingisfun

    Joined:
    May 26, 2016
    Posts:
    25
    Have to agree with dadude123 on this. However, should clarify you will need the RectTransform of the content size fitter / layout component. Simply cache this and you'll have it working much quicker.

    I would HIGHLY recommend that you do not use GetComponent, rather cache the reference, because a GetComponent call is ridiculously expensive.
     
    ltomov and Raptosauru5 like this.
  8. Masea1

    Masea1

    Joined:
    Jun 9, 2018
    Posts:
    26
    I am not quite sure why but in my case, it was enough for me to only force update the canvas when Pixel Perfect option was enabled in the Canvas component to get use of the size fitter. However, when I disabled the option, I had to add those two lines and so refresh the Layout component as well. This needs to be revisited ASAP.
     
    vinnieg and unnanego like this.
  9. uzisho

    uzisho

    Joined:
    Jul 3, 2019
    Posts:
    9
    LayoutRebuilder.ForceRebuildLayoutImmediate worked wonders for me.
    Thanks dadude123!

     
    Mn0dsew and bettywhite like this.
  10. houskyd

    houskyd

    Joined:
    Feb 10, 2020
    Posts:
    3
    It's worth noting that LayoutRebuilder.ForceRebuildLayoutImmediate will only work properly if the game object is active.
     
    ltomov likes this.
  11. 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;
     
    akuno likes this.
  12. pofu_lu

    pofu_lu

    Joined:
    Feb 5, 2019
    Posts:
    3
  13. WILEz1975

    WILEz1975

    Joined:
    Mar 23, 2013
    Posts:
    343
    To me it only works in a coroutine:

    Code (CSharp):
    1.     public IEnumerator UpdateRect() {
    2.         if (GetComponent<VerticalLayoutGroup>())
    3.         GetComponent<VerticalLayoutGroup>().enabled = false;
    4.         yield return new WaitForSeconds(0.1F);
    5.         if (GetComponent<VerticalLayoutGroup>())
    6.             GetComponent<VerticalLayoutGroup>().enabled = true;
    7.     }
     
    KingsHere and DerDicke like this.
  14. KingsHere

    KingsHere

    Joined:
    Nov 3, 2015
    Posts:
    3
    Thank you!

    Yes, it needs an update between disable and enable.

    To the new guys, I would recommend caching if you're calling this coroutine too often :
    Code (CSharp):
    1.  
    2.     public GameObject rootOfScrollContent;
    3.  
    4.     VerticalLayoutGroup verticalLayoutGroup;
    5.  
    6.     void Start()
    7.     {
    8.         verticalLayoutGroup = rootOfScrollContent.GetComponent<VerticalLayoutGroup>();
    9.         if (verticalLayoutGroup == null)
    10.         {
    11.             Debug.LogError("No VerticalLayoutGroup component on rootOfScrollContent exists.");
    12.         }
    13.         else
    14.         {
    15.             StartCoroutine(UpdateRect());
    16.         }
    17.     }
    18.  
    19.  
    20.     IEnumerator UpdateRect()
    21.     {
    22.         if (verticalLayoutGroup != null)
    23.         {
    24.             verticalLayoutGroup.enabled = false;
    25.             yield return new WaitForSeconds(0.1F);
    26.             verticalLayoutGroup.enabled = true;
    27.         }
    28.     }

    [It was working all fine once, all of a sudden started to get this error on 2019.4; where ContentSizeFitter wouldn't update the height of scrollContent. Spent almost 3 hours on this trivial thing . Really didn't want to write this hack, as there should be some editor only thing.]
     
    alargastudio likes this.
  15. JonesEri07

    JonesEri07

    Joined:
    Aug 21, 2019
    Posts:
    1

    THANK YOU! Looked everywhere for a solution and this worked perfectly.
     
  16. gustavoluciogd

    gustavoluciogd

    Joined:
    Oct 23, 2019
    Posts:
    1
    Spend a day looking for this, thanks so much
     
  17. Tymianek

    Tymianek

    Joined:
    May 16, 2015
    Posts:
    72
    I tried all of these and none of them work. My UI still takes 3 frames to stabilise. I tried ForceRebuild on every UI element, disabling and enabling all LayoutGroups OnEnable, and more.
     
    Last edited: Feb 16, 2021
  18. look001

    look001

    Joined:
    Mar 23, 2017
    Posts:
    65
    It is important to rebuild the layout from the bottom up, sice the parent objects will need the size of its children. You have to make sure to rebuild the children first an then the parent. Here is an example how you could implement it:
    Code (CSharp):
    1.  
    2. public class ContentFitterRefresh : MonoBehaviour
    3. {
    4.     private void Awake()
    5.     {
    6.         RefreshContentFitters();
    7.     }
    8.  
    9.     public void RefreshContentFitters()
    10.     {
    11.         var rectTransform = (RectTransform)transform;
    12.         RefreshContentFitter(rectTransform);
    13.     }
    14.  
    15.     private void RefreshContentFitter(RectTransform transform)
    16.     {
    17.         if (transform == null || !transform.gameObject.activeSelf)
    18.         {
    19.             return;
    20.         }
    21.      
    22.         foreach (RectTransform child in transform)
    23.         {
    24.             RefreshContentFitter(child);
    25.         }
    26.  
    27.         var layoutGroup = transform.GetComponent<LayoutGroup>();
    28.         var contentSizeFitter = transform.GetComponent<ContentSizeFitter>();
    29.         if (layoutGroup != null)
    30.         {
    31.             layoutGroup.SetLayoutHorizontal();
    32.             layoutGroup.SetLayoutVertical();
    33.         }
    34.  
    35.         if (contentSizeFitter != null)
    36.         {
    37.             LayoutRebuilder.ForceRebuildLayoutImmediate(transform);
    38.         }
    39.     }
    40. }
    Attach the script to your upper most contentsizefitter. Call RefreshContentFitters to rebuild contentsizefitters and layoutgroups in the right order.
     
    Last edited: Feb 20, 2021
    raydekk and Tymianek like this.
  19. raydekk

    raydekk

    Joined:
    Mar 14, 2013
    Posts:
    73
    You, sir, are a life saver.

    Thank you!
     
  20. subramaniyanvgatmoback

    subramaniyanvgatmoback

    Joined:
    Oct 13, 2016
    Posts:
    14
    Crystal clear explanation thank you so much.
     
unityunity