Does the Content Size Fitter work?

I’m making a dialogue bubble where it will automatically resize the bubble to the text’s size.


I have the content size fitter attached to the background image.

But if the Text overflows, the background image doesn’t change size!

Does the content size fitter not work with TextMesh Pro?

2 Likes

Try setting the vertical fit to preferred size. Unconstrained does nothing.

1 Like

It did this…

1 Like

The Content Size Fitter does work but you have to make sure you are using the TextMeshPro UGUI Component which is located in Create - UI - TextMeshPro - Text

2 Likes

It is using TextMeshPro UGUI:

If your content size fitter is not connected to your speech bubble sprite, it will not expand your speech bubble; only the text box.

You have to add some Layout component like Horizontal or Vertical Layout Group.

18 Likes

Thank you for your help!
I got it working :slight_smile:

I’m using the same settings but my panel expands the full height of the canvas. Below are the behaviors when I change my vertical fit:

  • Unconstrained: No change
  • Min Size: Panel’s height (and child TextMeshPro UGUI height) set to 0
  • Preferred Size: Panel’s height (and child TextMeshPro UGUI height) set to canvas height
1 Like

Same is happening to me. Is this broken now?

Sure seems like ContentSizeFitter is broken. I’m using 2018.2.12f

1 Like

I don’t recall adding a ContentSizeFitter alone on a parent object like a UI Button working. Adding either an HorizontalLayoutGroup or VerticalLayoutGroup still appears to work in addition to the ContentSizeFitter does still appear to work. See the example I posted above.

2 Likes

This may be overkill for a minor annoyance, but if you prefer, you could use this component:

using UnityEngine;
using TMPro;

// Resizes this object based on the preferred size of a TMP Text
[ExecuteInEditMode]
public class TextSizer : MonoBehaviour
{
    public TMP_Text text;
    public Vector2 padding;
    public Vector2 maxSize = new Vector2(1000, float.PositiveInfinity);
    public Vector2 minSize;

    public enum Mode
    {
        None        = 0,
        Horizontal  = 0x1,
        Vertical    = 0x2,
        Both        = Horizontal | Vertical
    }
    public Mode controlAxes = Mode.Both;

    protected string lastText = null;
    protected Vector2 lastSize;
    protected bool forceRefresh = false;

    protected virtual float MinX { get {
            if ((controlAxes & Mode.Horizontal) != 0) return minSize.x;
            return GetComponent<RectTransform>().rect.width - padding.x;
        } }
    protected virtual float MinY { get {
            if ((controlAxes & Mode.Vertical) != 0) return minSize.y;
            return GetComponent<RectTransform>().rect.height - padding.y;
        } }
    protected virtual float MaxX { get {
            if ((controlAxes & Mode.Horizontal) != 0) return maxSize.x;
            return GetComponent<RectTransform>().rect.width - padding.x;
        } }
    protected virtual float MaxY { get {
            if ((controlAxes & Mode.Vertical) != 0) return maxSize.y;
            return GetComponent<RectTransform>().rect.height - padding.y;
        } }

    protected virtual void Update ()
    {
        RectTransform rt = GetComponent<RectTransform>();
        if (text != null && (text.text != lastText || lastSize != rt.rect.size || forceRefresh))
        {
            lastText = text.text;
            Vector2 preferredSize = text.GetPreferredValues(MaxX, MaxY);
            preferredSize.x = Mathf.Clamp(preferredSize.x, MinX, MaxX);
            preferredSize.y = Mathf.Clamp(preferredSize.y, MinY, MaxY);
            preferredSize += padding;

            if ((controlAxes & Mode.Horizontal) != 0)
            {
                rt.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, preferredSize.x);
            }
            if ((controlAxes & Mode.Vertical) != 0)
            {
                rt.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, preferredSize.y);
            }
            lastSize = rt.rect.size;
            forceRefresh = false;
        }
    }

    // Forces a size recalculation on next Update
    public virtual void Refresh()
    {
        forceRefresh = true;
    }
}
13 Likes

Actually this is the most viable solution I found, here’s a bit more optimized version of @Antistone 's code, note that if you destroy the TMP component or change its reference at runtime you’ll have to call Refresh()

[ExecuteInEditMode]
public class TextSizer : MonoBehaviour
{
    public TMP_Text Text;
    public bool ResizeTextObject = true;
    public Vector2 Padding;
    public Vector2 MaxSize = new Vector2(1000, float.PositiveInfinity);
    public Vector2 MinSize;
    public Mode ControlAxes = Mode.Both;
   
    [Flags]
    public enum Mode
    {
        None        = 0,
        Horizontal  = 0x1,
        Vertical    = 0x2,
        Both        = Horizontal | Vertical
    }

    private string _lastText;
    private Mode _lastControlAxes = Mode.None;
    private Vector2 _lastSize;
    private bool _forceRefresh;
    private bool _isTextNull = true;
    private RectTransform _textRectTransform;
    private RectTransform _selfRectTransform;

    protected virtual float MinX { get {
        if ((ControlAxes & Mode.Horizontal) != 0) return MinSize.x;
        return _selfRectTransform.rect.width - Padding.x;
    } }
    protected virtual float MinY { get {
        if ((ControlAxes & Mode.Vertical) != 0) return MinSize.y;
        return _selfRectTransform.rect.height - Padding.y;
    } }
    protected virtual float MaxX { get {
        if ((ControlAxes & Mode.Horizontal) != 0) return MaxSize.x;
        return _selfRectTransform.rect.width - Padding.x;
    } }
    protected virtual float MaxY { get {
        if ((ControlAxes & Mode.Vertical) != 0) return MaxSize.y;
        return _selfRectTransform.rect.height - Padding.y;
    } }

    protected virtual void Update ()
    {
        if (!_isTextNull && (Text.text != _lastText || _lastSize != _selfRectTransform.rect.size || _forceRefresh || ControlAxes != _lastControlAxes))
        {
            var preferredSize = Text.GetPreferredValues(MaxX, MaxY);
            preferredSize.x = Mathf.Clamp(preferredSize.x, MinX, MaxX);
            preferredSize.y = Mathf.Clamp(preferredSize.y, MinY, MaxY);
            preferredSize += Padding;

            if ((ControlAxes & Mode.Horizontal) != 0)
            {
                _selfRectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, preferredSize.x);
                if (ResizeTextObject)
                {
                    _textRectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, preferredSize.x);
                }
            }
            if ((ControlAxes & Mode.Vertical) != 0)
            {
                _selfRectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, preferredSize.y);
                if (ResizeTextObject)
                {
                    _textRectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, preferredSize.y);
                }
            }
           
            _lastText = Text.text;
            _lastSize = _selfRectTransform.rect.size;
            _lastControlAxes = ControlAxes;
            _forceRefresh = false;
        }
    }

    // Forces a size recalculation on next Update
    public virtual void Refresh ()
    {
        _forceRefresh = true;
       
        _isTextNull = Text == null;
        if (Text) _textRectTransform = Text.GetComponent<RectTransform>();
        _selfRectTransform = GetComponent<RectTransform>();
    }
    private void OnValidate ()
    {
        Refresh();
    }
}

The object on which this component is attached get resized according to the place that the TMP object and optionally resizes it via ResizeTextObject

20 Likes

Thanks for this! I just wanted to add that if you add/remove stuff from your menus dynamically its a good idea to add this to the code:

public virtual void Start()
{
    Refresh();
}

That makes sure the size is recalculated when the object is spawned.

2 Likes

1 day of work lost trying to tweek the crap out of the devilish combo Horizontal Layout Group + Content Size Fitter.
And then, finding this post with its script that just works at the second I hitted Play button.

Thank you @Antistone and thank you @Akrone for the great scripts.

1 Like

Thank you! :slight_smile:

Hey there, the above-mentioned script did not really work for me, so I poked around a bit more, and found a solution that I thought could be relevant:

With LayoutRebuilder.ForceRebuildLayoutImmediate(Rect2D) you can manually update the entire layout, or parts of the layout, which solves the problem with nested content size fitters and layout groups.

I used it to update the direct parent of the text mesh, then the parent of that. Now content size fitters, text mesh pro texts and layout groups work in perfect harmony.

1 Like

Ditto, what has been said about the script on this page.

Attach it to the Text object (converted mine to “TextMeshProUGUI” object in the script - otherwise left as it was). Fill in the parameters in the UI. Boom - works. Thank you!

Just to chime in here… I can’t get ContentSizeFitter to work properly, either. In my configuration it seems to work OK when the text gets large, but when the text is short, it refuses to shrink the width down less than 52, which is way too big.

(And no, it’s not the padding; if I set the padding values to 0, then the minimum width becomes 62, which looks exactly the same and is just as wrong.)

I wasted a good 20 minutes on this, then installed the script above (modified to operate on TextMeshProUGUI, like @jtd200 ) and now everything’s working fine.