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?
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.
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 - Redirecting to latest version of com.unity.ugui
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.
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class DyanamicUIFitter : MonoBehaviour {
// Use this for initialization
void Start () {
//Get the Layout Min Height from the Rect Transform Height
//Wait for the first frame
Invoke("GetRectSet", 0.01f); ///on your start function to delay it a bit.
}
void GetRectSet(){
//Set the Layout Min Value equivaline to RectTransform
this.GetComponent<LayoutElement>().minHeight = this.GetComponent<RectTransform> ().rect.height;
}
}
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.
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
private void Start()
{
StartCoroutine(WaitUntilEndOfFrame());
}
IEnumerator WaitUntilEndOfFrame()
{
yield return new WaitForEndOfFrame();
//Do your stuff
}
}
In your Start method you could use 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.
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.
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.
using UnityEngine;
public class ForceCanvasUpdate : MonoBehaviour
{
void Start()
{
Canvas.ForceUpdateCanvases();
}
}
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.
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.
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
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
$(document).ready in JavaScript ensures the code runs after the HTML is fully loaded and parsed before images and other resources.
If Javascript code manipulate image or refer to a property in image run before image loaded, these code will not work properly.
The same thing apply to canvas in Unity.
A canvas 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. This means that in the Start callback and the first Update callback, the layout and content under the canvas may not be up-to-date.
Source: Unity - Scripting API: Canvas.ForceUpdateCanvases
Canvas.ForceUpdateCanvases() will help force all canvases to update their content.
Imagine elements are inside a panel with a vertical/horizontal layout component or a grid layout group.
Only after run ForceUpdateCanvases() function, you can call to:
width = this.GetComponent<RectTransform>().rect.width;
height = this.GetComponent<RectTransform>().rect.height;
position = this.transform.localPosition.y;
This is because, as I say above, you can only call to a property of Image after Image loaded. Similarly, you can only call to a property of a cell inside grid layout group after grid layout group is rendered.
+Using Invoke(“GetRectSet”, 0.01f); as mention by OJ3D ,
+Using Coroutine as mention by Suduckgames
->are just temporary fixes , not recommended.
+This is NOT a “absurd bug and quite embarrassing it has been around for 3 years without being fixed or addressed in any way by Unity.” as mention by ehudcandivore.
Using Canvas.ForceUpdateCanvases(); as mention by Tymianek is the best overall solution to the problem.