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.
Hi there. I had a very similar setup for tooltips with the same issue. This solved my problem:
tooltTipText = "new tooltip \n second row";
Canvas.ForceUpdateCanvases(); // *
tooltTipText.transform.parent.GetComponent<HorizontalLayoutGroup>().enabled = false; // **
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.
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.
@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.
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.
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.
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.
EDIT:
Or if you have the Transform already you can use LayoutRebuilder.ForceRebuildLayoutImmediate(transform as RectTransform) to cast it.
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.
public IEnumerator UpdateRect() {
if (GetComponent<VerticalLayoutGroup>())
GetComponent<VerticalLayoutGroup>().enabled = false;
yield return new WaitForSeconds(0.1F);
if (GetComponent<VerticalLayoutGroup>())
GetComponent<VerticalLayoutGroup>().enabled = true;
}
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 :
public GameObject rootOfScrollContent;
VerticalLayoutGroup verticalLayoutGroup;
void Start()
{
verticalLayoutGroup = rootOfScrollContent.GetComponent<VerticalLayoutGroup>();
if (verticalLayoutGroup == null)
{
Debug.LogError("No VerticalLayoutGroup component on rootOfScrollContent exists.");
}
else
{
StartCoroutine(UpdateRect());
}
}
IEnumerator UpdateRect()
{
if (verticalLayoutGroup != null)
{
verticalLayoutGroup.enabled = false;
yield return new WaitForSeconds(0.1F);
verticalLayoutGroup.enabled = true;
}
}
[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.]
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.
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:
public class ContentFitterRefresh : MonoBehaviour
{
private void Awake()
{
RefreshContentFitters();
}
public void RefreshContentFitters()
{
var rectTransform = (RectTransform)transform;
RefreshContentFitter(rectTransform);
}
private void RefreshContentFitter(RectTransform transform)
{
if (transform == null || !transform.gameObject.activeSelf)
{
return;
}
foreach (RectTransform child in transform)
{
RefreshContentFitter(child);
}
var layoutGroup = transform.GetComponent<LayoutGroup>();
var contentSizeFitter = transform.GetComponent<ContentSizeFitter>();
if (layoutGroup != null)
{
layoutGroup.SetLayoutHorizontal();
layoutGroup.SetLayoutVertical();
}
if (contentSizeFitter != null)
{
LayoutRebuilder.ForceRebuildLayoutImmediate(transform);
}
}
}
Attach the script to your upper most contentsizefitter. Call RefreshContentFitters to rebuild contentsizefitters and layoutgroups in the right order.