Search Unity

[Solved] Can't get the rect.width / rect.height of an element when using layouts

Discussion in 'UGUI & TextMesh Pro' started by moure, Jan 6, 2016.

  1. moure

    moure

    Joined:
    Aug 18, 2013
    Posts:
    184
    Hi,
    i need to get the width and height dimension of a ui rect element during runtime to adjust the tiling/offset of a ui material texture.
    Code (CSharp):
    1. width = this.GetComponent<RectTransform>().rect.width;
    2. height = this.GetComponent<RectTransform>().rect.height;
    3. ratio = width / height;
    Everything works correctly until the elements are inside a panel with a vertical/horizontal layout component. Then even though the rect transform of the elements shows the correct values (grayed out), the above code returns zero for both width and height. I just need to read the above values, not change them (i know that the elements size is now controlled by the vertical/horizontal layout component)
    Is that functionality on purpose? The only way i can think of getting the values atm is temporarily disabling the layout component fetch the values and then reenabling it? Is there an easier way to get the values?

    Thanks
     
    N8W1nD, Tymianek, -chris and 2 others like this.
  2. moure

    moure

    Joined:
    Aug 18, 2013
    Posts:
    184
    Figured it out after a lot of search in the unity answers. I will add it here too in case someone has the same issue in the future.
    The problem was that i tried to get the width/height in my start function. But as it turns out the layout group scirpt (as well as content size fitters) need a frame to update before you can get the correct element size values. So you can just Invoke("GetSize", 0.01); on your start function to delay it a bit.
     
    grbulat, sildeflask, N8W1nD and 19 others like this.
  3. OJ3D

    OJ3D

    Joined:
    Feb 13, 2014
    Posts:
    33
    Moure - Awesome share. I made an automatic content size fitter, but when you instantiate all the elements at run time they wouldn't space out evenly within the Layout group ( Horizontal). I used unity's guide - http://docs.unity3d.com/Manual/HOWTO-UIFitContentSize.html

    Anyway, found out the Parent UI element needed a Min size which would show up in the Rect-transform Height but wouldn't pass it at all. Then only will the Layout group's register the spacing. So I needed to steal that info and slap it into the Layout Elements Min size. Fortunately what you did worked, in combination to the Guide above. The small bit of code is below.

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using UnityEngine.UI;
    4.  
    5. public class DyanamicUIFitter : MonoBehaviour {
    6.  
    7.     // Use this for initialization
    8.     void Start () {
    9.  
    10.         //Get the Layout Min Height from the Rect Transform Height
    11.         //Wait for the first frame
    12.         Invoke("GetRectSet", 0.01f); ///on your start function to delay it a bit.    
    13.  
    14.     }
    15.  
    16.     void GetRectSet(){
    17.         //Set the Layout Min Value equivaline to RectTransform
    18.         this.GetComponent<LayoutElement>().minHeight = this.GetComponent<RectTransform> ().rect.height;
    19.  
    20.     }
    21.  
    22.  
    23. }
    24.  
    Thanks again!
    OJ
     
    -chris, ramzx, Elfonz and 3 others like this.
  4. Xerosigma

    Xerosigma

    Joined:
    Mar 1, 2012
    Posts:
    27
    This saved my life. Thanks a ton for the solution.
     
  5. Undertaker-Infinity

    Undertaker-Infinity

    Joined:
    May 2, 2014
    Posts:
    112
    I just hit the same snag.
    Glad to know I was on the right track.
    Also sad to know I was on the right track. I'll have to eat a 1-frame jump in my UI, which is ugly.
    Oh well. Sorry about the necro, but this saved me creating a new thread.
     
    Rodolfo-Rubens likes this.
  6. Suduckgames

    Suduckgames

    Joined:
    Nov 28, 2016
    Posts:
    218
    Just a note. As this is valid in most of the cases. take into account that if the first frame take more time than 0.01f it won't calculate it as it should.


    I recommend to use a Courutine waiting for the end of frame


    Code (CSharp):
    1.  
    2.  
    3.   private void Start()
    4.     {
    5.         StartCoroutine(WaitUntilEndOfFrame());
    6.     }
    7.  
    8.  
    9.   IEnumerator WaitUntilEndOfFrame()
    10.     {
    11.         yield return new WaitForEndOfFrame();
    12.         //Do your stuff
    13.     }
    14.     }
     
    Last edited: Oct 19, 2018
    PM79, sildeflask, ferko_12 and 6 others like this.
  7. smallstep

    smallstep

    Joined:
    Jan 25, 2016
    Posts:
    34
    This also works:

    Code (CSharp):
    1.  
    2. IEnumerator Start()
    3. {
    4.    //normal Start stuff...
    5.    yield return null;
    6.    // get rect.width... (runs after 1 frame)
    7. }
    8.  
     
    lucbloom, tknippenberg and ATIliev like this.
  8. Tymianek

    Tymianek

    Joined:
    May 16, 2015
    Posts:
    97
    In your Start method you could use
    Code (CSharp):
    1.  Canvas.ForceUpdateCanvases();
    before accessing the rectTransform.rect.width property. That will ensure layout dimensions are calculated and rect.width and rect.height will output proper values.
    Edit: It's fine to call it once in Awake or Start, but don't use it in Update.
     
    Last edited: Jul 7, 2022
    emredesu, lucbloom, cookiebro and 2 others like this.
  9. ehudcandivore

    ehudcandivore

    Joined:
    Jul 18, 2018
    Posts:
    12
    Thanks Tymianek, that works...
    I agree it's an absurd bug and quite embarrassing it has been around for 3 years without being fixed or addressed in any way by Unity.
     
  10. hungrybelome

    hungrybelome

    Joined:
    Dec 31, 2014
    Posts:
    336
    You can also try
    Code (CSharp):
    1. LayoutRebuilder.ForceRebuildLayoutImmediate((RectTransform) HorizontalLayoutGroup.transform);
    2.  
     
    Tymianek and pmjo like this.
  11. Tymianek

    Tymianek

    Joined:
    May 16, 2015
    Posts:
    97
    It is also important where your script is in the Execution Order.
    In the example below I needed to force canvas script before TMPro is calculated, otherwise it would get wrong width on the first frame and my Scroll View was getting scrolled for no reason.
    upload_2020-1-20_17-23-38.png
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class ForceCanvasUpdate : MonoBehaviour
    4. {
    5.     void Start()
    6.     {
    7.         Canvas.ForceUpdateCanvases();
    8.     }
    9. }
     
  12. bhupiister

    bhupiister

    Joined:
    Dec 13, 2019
    Posts:
    42
    Thanks dear, you cleared the concept of waiting for some time and @Suduckgames gave a valid point to wait till the end of the frame.

    Here is my code

    Code (CSharp):
    1. void Start()
    2. {
    3.       WaitUntilEndOfFrame();
    4. }
    Code (CSharp):
    1. IEnumerator WaitUntilEndOfFrame()
    2.     {
    3.         yield return new WaitForEndOfFrame();
    4.         //Do your stuff
    5.         UpdateDimTextBC();
    6.     }
    //For calling a method UpdateDimTextBC
    Code (CSharp):
    1.     public void UpdateDimTextBC()
    2.     {
    3.         foreach (Transform dimtext1 in canvas1.transform)
    4.         {
    5.             if (dimtext1.gameObject.activeSelf)
    6.             {
    7.                 var widthDT = dimtext1.gameObject.GetComponent<RectTransform>().rect.width;
    8.             var heightDT = dimtext1.gameObject.GetComponent<RectTransform>().rect.height;
    9.             Debug.LogWarningFormat("Box collider width:{0} height:{1}", widthDT, heightDT);
    10.             var UIPrefabBC = dimtext1.gameObject.GetComponent<BoxCollider>();
    11.             UIPrefabBC.size = new Vector3(widthDT, heightDT, 0);
    12.             }
    13.         }
    14.     }
    Anyone wanting to use content size fitter and box collider and wants to update it automatically, this combination worked for me.
     
    Last edited: May 25, 2020
  13. Aykutkaraca

    Aykutkaraca

    Joined:
    Jan 4, 2018
    Posts:
    33
    It is so irritating. I have been wasting time days and days to find this out. It is really demotivating me. I am an indie developer and I am demotivated by so many quirks like that I face every day. It is so tiring. Thanks for saving my time.

    Canvas.ForceUpdateCanvases();

    worked for me.
     
  14. Max_Bol

    Max_Bol

    Joined:
    May 12, 2014
    Posts:
    168
    I would like to thank all of you for pointing this out and giving potential solutions.

    When it comes to dynamic adaptation of the UI for mobile devices, Unity as an engine requires a lot of foundation work, hence why so little games and app actually go the whole mile to make something new and exciting to use.

    I'm making use of layer groups to manage rows (or columns) of buttons and I'm adapting the height of certain groups based on the width of their individual button. (Especially useful for square/circle buttons.) I'm doing all the "manual" work because the screen ratio difference between devices can be really damn huge nowadays. Taking the whole height and width of a screen, looking up how much "space" is available, putting conditions whenever the space is within A, B, C or D so that the UI looks good on all sizes without having button too small or too big or taking too much of the screen... or too little.

    It's an even bigger nightmare when you allow the user to rotate their device.
    (Before someone mention that you can lock the screen rotation of the app, one of the most common complain of certain games is how the device has to be held to play the game. For example, some people like to be able to play when coming back from work where 1 hand might hold a briefcase while some people might be moving in a tight space where holding the device vertically offers a bit less chance to hit someone close by with the device by mistake.

    Hence, most of my mobile projects (when applicable) are made to be usable in both vertical and horizontal which involve 2 UI with different layout that fits into any kind of screen ratio.

    Using the Coroutine wit WaitForEndOfFrame() is the simplest and easiest way for me since it can be stopped and started when necessary without any risk of "bleeding" the processes.
     
  15. stfunity

    stfunity

    Joined:
    Sep 9, 2018
    Posts:
    65
    Reading through this thread and one other, we arrived at:
     

    Attached Files:

  16. Mpx83

    Mpx83

    Joined:
    Apr 4, 2023
    Posts:
    21
    I had the same problem and I want to thanks you all for the ideas I got from this post. I had a similar problem. For me the best solution was the WaitForEndofFrame. However since I was updating the content dynamically several types I was not too happy to need to always use coroutines to get the correct height and width.
    So i come auto with (another) solution

    Code (CSharp):
    1.     private void OnRectTransformDimensionsChange()
    2.     {
    3.         rectWidth = contentRectTransform.rect.width;
    4.         rectHeight = contentRectTransform.rect.height;
    5.      }
    where contentRectTransform is the recttransform of the object to which the script is attached. Using on recttransformDimensionsChange everytime the recttransform changes the width and height are automatically updated.
    Just posting in case this can help as the the options helped me