Search Unity

TextMesh Pro Does the Content Size Fitter work?

Discussion in 'UGUI & TextMesh Pro' started by BlockFade, Jul 23, 2017.

  1. BlockFade

    BlockFade

    Joined:
    Jan 28, 2017
    Posts:
    25
    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?
     
    ben4d85 and Lyrcaxis like this.
  2. User340

    User340

    Joined:
    Feb 28, 2007
    Posts:
    3,001
    Try setting the vertical fit to preferred size. Unconstrained does nothing.
     
    _TheFuture_ likes this.
  3. BlockFade

    BlockFade

    Joined:
    Jan 28, 2017
    Posts:
    25
    It did this...
     
    DragonCoder likes this.
  4. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    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
     
    _TheFuture_ and art7pro like this.
  5. BlockFade

    BlockFade

    Joined:
    Jan 28, 2017
    Posts:
    25
    It is using TextMeshPro UGUI:
     
  6. MrHappyKiller

    MrHappyKiller

    Joined:
    Feb 8, 2016
    Posts:
    29
    If your content size fitter is not connected to your speech bubble sprite, it will not expand your speech bubble; only the text box.
     
  7. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    You have to add some Layout component like Horizontal or Vertical Layout Group.

    upload_2017-7-22_20-14-56.png
     
    Octarina, DTAli, ben4d85 and 14 others like this.
  8. BlockFade

    BlockFade

    Joined:
    Jan 28, 2017
    Posts:
    25
    Thank you for your help!
    I got it working :)
     
  9. nrvllrgrs

    nrvllrgrs

    Joined:
    Jan 12, 2010
    Posts:
    62
    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
     
    Nodata likes this.
  10. Nodata

    Nodata

    Joined:
    Oct 29, 2014
    Posts:
    1
    Same is happening to me. Is this broken now?
     
  11. warreneng

    warreneng

    Joined:
    Mar 13, 2017
    Posts:
    8
    Sure seems like ContentSizeFitter is broken. I'm using 2018.2.12f
     
    Zante likes this.
  12. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    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.
     
    ben4d85 and LilGames like this.
  13. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,836
    This may be overkill for a minor annoyance, but if you prefer, you could use this component:

    Code (CSharp):
    1. using UnityEngine;
    2. using TMPro;
    3.  
    4. // Resizes this object based on the preferred size of a TMP Text
    5. [ExecuteInEditMode]
    6. public class TextSizer : MonoBehaviour
    7. {
    8.     public TMP_Text text;
    9.     public Vector2 padding;
    10.     public Vector2 maxSize = new Vector2(1000, float.PositiveInfinity);
    11.     public Vector2 minSize;
    12.  
    13.     public enum Mode
    14.     {
    15.         None        = 0,
    16.         Horizontal  = 0x1,
    17.         Vertical    = 0x2,
    18.         Both        = Horizontal | Vertical
    19.     }
    20.     public Mode controlAxes = Mode.Both;
    21.  
    22.     protected string lastText = null;
    23.     protected Vector2 lastSize;
    24.     protected bool forceRefresh = false;
    25.  
    26.     protected virtual float MinX { get {
    27.             if ((controlAxes & Mode.Horizontal) != 0) return minSize.x;
    28.             return GetComponent<RectTransform>().rect.width - padding.x;
    29.         } }
    30.     protected virtual float MinY { get {
    31.             if ((controlAxes & Mode.Vertical) != 0) return minSize.y;
    32.             return GetComponent<RectTransform>().rect.height - padding.y;
    33.         } }
    34.     protected virtual float MaxX { get {
    35.             if ((controlAxes & Mode.Horizontal) != 0) return maxSize.x;
    36.             return GetComponent<RectTransform>().rect.width - padding.x;
    37.         } }
    38.     protected virtual float MaxY { get {
    39.             if ((controlAxes & Mode.Vertical) != 0) return maxSize.y;
    40.             return GetComponent<RectTransform>().rect.height - padding.y;
    41.         } }
    42.  
    43.     protected virtual void Update ()
    44.     {
    45.         RectTransform rt = GetComponent<RectTransform>();
    46.         if (text != null && (text.text != lastText || lastSize != rt.rect.size || forceRefresh))
    47.         {
    48.             lastText = text.text;
    49.             Vector2 preferredSize = text.GetPreferredValues(MaxX, MaxY);
    50.             preferredSize.x = Mathf.Clamp(preferredSize.x, MinX, MaxX);
    51.             preferredSize.y = Mathf.Clamp(preferredSize.y, MinY, MaxY);
    52.             preferredSize += padding;
    53.  
    54.             if ((controlAxes & Mode.Horizontal) != 0)
    55.             {
    56.                 rt.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, preferredSize.x);
    57.             }
    58.             if ((controlAxes & Mode.Vertical) != 0)
    59.             {
    60.                 rt.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, preferredSize.y);
    61.             }
    62.             lastSize = rt.rect.size;
    63.             forceRefresh = false;
    64.         }
    65.     }
    66.  
    67.     // Forces a size recalculation on next Update
    68.     public virtual void Refresh()
    69.     {
    70.         forceRefresh = true;
    71.     }
    72. }
     
    RaL, VotaVader, Crokett and 9 others like this.
  14. Akrone

    Akrone

    Joined:
    Jun 4, 2018
    Posts:
    2
    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()
    Code (CSharp):
    1.  
    2. [ExecuteInEditMode]
    3. public class TextSizer : MonoBehaviour
    4. {
    5.     public TMP_Text Text;
    6.     public bool ResizeTextObject = true;
    7.     public Vector2 Padding;
    8.     public Vector2 MaxSize = new Vector2(1000, float.PositiveInfinity);
    9.     public Vector2 MinSize;
    10.     public Mode ControlAxes = Mode.Both;
    11.    
    12.     [Flags]
    13.     public enum Mode
    14.     {
    15.         None        = 0,
    16.         Horizontal  = 0x1,
    17.         Vertical    = 0x2,
    18.         Both        = Horizontal | Vertical
    19.     }
    20.  
    21.     private string _lastText;
    22.     private Mode _lastControlAxes = Mode.None;
    23.     private Vector2 _lastSize;
    24.     private bool _forceRefresh;
    25.     private bool _isTextNull = true;
    26.     private RectTransform _textRectTransform;
    27.     private RectTransform _selfRectTransform;
    28.  
    29.     protected virtual float MinX { get {
    30.         if ((ControlAxes & Mode.Horizontal) != 0) return MinSize.x;
    31.         return _selfRectTransform.rect.width - Padding.x;
    32.     } }
    33.     protected virtual float MinY { get {
    34.         if ((ControlAxes & Mode.Vertical) != 0) return MinSize.y;
    35.         return _selfRectTransform.rect.height - Padding.y;
    36.     } }
    37.     protected virtual float MaxX { get {
    38.         if ((ControlAxes & Mode.Horizontal) != 0) return MaxSize.x;
    39.         return _selfRectTransform.rect.width - Padding.x;
    40.     } }
    41.     protected virtual float MaxY { get {
    42.         if ((ControlAxes & Mode.Vertical) != 0) return MaxSize.y;
    43.         return _selfRectTransform.rect.height - Padding.y;
    44.     } }
    45.  
    46.     protected virtual void Update ()
    47.     {
    48.         if (!_isTextNull && (Text.text != _lastText || _lastSize != _selfRectTransform.rect.size || _forceRefresh || ControlAxes != _lastControlAxes))
    49.         {
    50.             var preferredSize = Text.GetPreferredValues(MaxX, MaxY);
    51.             preferredSize.x = Mathf.Clamp(preferredSize.x, MinX, MaxX);
    52.             preferredSize.y = Mathf.Clamp(preferredSize.y, MinY, MaxY);
    53.             preferredSize += Padding;
    54.  
    55.             if ((ControlAxes & Mode.Horizontal) != 0)
    56.             {
    57.                 _selfRectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, preferredSize.x);
    58.                 if (ResizeTextObject)
    59.                 {
    60.                     _textRectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, preferredSize.x);
    61.                 }
    62.             }
    63.             if ((ControlAxes & Mode.Vertical) != 0)
    64.             {
    65.                 _selfRectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, preferredSize.y);
    66.                 if (ResizeTextObject)
    67.                 {
    68.                     _textRectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, preferredSize.y);
    69.                 }
    70.             }
    71.            
    72.             _lastText = Text.text;
    73.             _lastSize = _selfRectTransform.rect.size;
    74.             _lastControlAxes = ControlAxes;
    75.             _forceRefresh = false;
    76.         }
    77.     }
    78.  
    79.     // Forces a size recalculation on next Update
    80.     public virtual void Refresh ()
    81.     {
    82.         _forceRefresh = true;
    83.        
    84.         _isTextNull = Text == null;
    85.         if (Text) _textRectTransform = Text.GetComponent<RectTransform>();
    86.         _selfRectTransform = GetComponent<RectTransform>();
    87.     }
    88.     private void OnValidate ()
    89.     {
    90.         Refresh();
    91.     }
    92. }
    93.  
    The object on which this component is attached get resized according to the place that the TMP object and optionally resizes it via ResizeTextObject
     
    Last edited: Jul 3, 2019
  15. RavenmoreArt

    RavenmoreArt

    Joined:
    Aug 28, 2014
    Posts:
    4
    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:

    Code (csharp):
    1.  
    2. public virtual void Start()
    3. {
    4.     Refresh();
    5. }
    6.  
    That makes sure the size is recalculated when the object is spawned.
     
    Missile_ and Erveon like this.
  16. Diablo404

    Diablo404

    Joined:
    Mar 15, 2013
    Posts:
    136
    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.
     
    JoeStrout likes this.
  17. piginhat

    piginhat

    Joined:
    Feb 17, 2016
    Posts:
    96
    Thank you! :)
     
  18. DavidLeertasteW

    DavidLeertasteW

    Joined:
    Dec 17, 2017
    Posts:
    5
    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.
     
    FerranIlla likes this.
  19. jtd200

    jtd200

    Joined:
    Dec 16, 2016
    Posts:
    7
    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!
     
  20. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    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.

    upload_2020-10-27_6-49-48.png

    upload_2020-10-27_6-50-14.png

    (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.
     
  21. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,836
    For the record, TMP_Text is a superclass of TextMeshProUGUI. You can assign a TextMeshProUGUI to the variable without modifying it.
     
    Missile_ and JoeStrout like this.
  22. SINePrime

    SINePrime

    Joined:
    Jan 24, 2019
    Posts:
    54
    I had a similar problem. My LayoutGroup would not shrink below 600, a seemingly random number.

    I determined that the Image I had on the parent RectTransform was forcing the Content Size Fitter to expand. I fixed this by:
    • putting the Image as a child of the Content Size Fitter
    • putting a LayoutElement on the image and setting "Ignore Layout" to true
    • setting the anchors of the background image to expand to fit
    Having attached components drive the ContentSizeFitter is definitely a gotcha. Hopefully this will help anyone else who had the same problem.
     
  23. Ultroman

    Ultroman

    Joined:
    Mar 10, 2014
    Posts:
    110
    Content Size Fitter still seems to mess up its scaling, when ticking on "Use Child Scale" for both axes on the Layout Group and the children have a scale different from 1. See my post here.

    • With a Vertical Layout Group, the Content Size Fitter ignores the child's scale on the horizontal axis.
    • With a Horizontal Layout Group, the Content Size Fitter ignores the child's scale on the vertical axis.

    This is driving me insane! If this worked properly, half my problems with Unity UI would go away instantly.

    Another chunk would go away, if when a child is changed while underneath a parent which has a Content Size Fitter on, it should automatically prompt an update on the parent, so we don't have to make weird hacks to force rebuilds or manually implement Max Size (which should already be a thing in Unity UI, e.g. on Layout Element). When there is no built-in solution for something trivial like that, people start doing nasty things like this to get around it.
    We need dynamic UI for games. Why is a dynamically sized textbox this difficult to make?
     
    Last edited: Jan 8, 2021
  24. Apparaten_

    Apparaten_

    Joined:
    Jul 9, 2013
    Posts:
    45
    The TMP_Text component has a builtin method called GetPreferredValues(string)
    It returns the prefered size based on the text.
    use this to size ex the parent component accordingly.
     
    Zapan15 and vselockov like this.
  25. SilentV4

    SilentV4

    Joined:
    Feb 21, 2020
    Posts:
    2
  26. unity_cic-PNqQMQ9LKA

    unity_cic-PNqQMQ9LKA

    Joined:
    Oct 30, 2020
    Posts:
    6

    This works, go for it right away, thank you so much bro!
     
  27. vselockov

    vselockov

    Joined:
    May 10, 2018
    Posts:
    10
    Another simple script if content size fitter doesn't work with rects:
    Code (CSharp):
    1. [ExecuteInEditMode]
    2. public class TextFitter : MonoBehaviour
    3. {
    4.     public TMPro.TextMeshProUGUI text;
    5.     public RectTransform rect;
    6.     public float margin = 0f;
    7.    
    8.     void Update()
    9.     {
    10.         rect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, text.GetPreferredValues().x+margin);
    11.     }
    12. }
    13.  
     
  28. Mowtine

    Mowtine

    Joined:
    Aug 17, 2014
    Posts:
    19
    If anyone has trouble like me with the above scripts not working when built, my issue was OnValidate() not being triggered in a build (or the set values being saved in the build). Have the Refresh function be called on Awake or before triggering an update to fix that.
     
  29. spiritworld

    spiritworld

    Joined:
    Nov 26, 2014
    Posts:
    29
    I noticed GetPreferredValues returns height of the character and width of the text. Effectively my speech bubble stays vertically short (character height) and expands only horizontally. Obviously the textmeshpro can't know where to start wrapping the text so I added some calculations to make enough height.

    Using horizontal layout group and contentfitter with both "min size"

    Code (CSharp):
    1.      
    2.         const float MAX_WIDTH = 500f;
    3.         const float LINE_HEIGHT = 80f;
    4.         public IEnumerator TypeDialogText(string inputText)
    5.         {
    6.             isDialogReady = false;
    7.             dialogText.maxVisibleCharacters = 0;
    8.             dialogText.SetText(inputText);
    9.             Vector2 preferredSize = dialogText.GetPreferredValues();
    10.             if (preferredSize.x > MAX_WIDTH)
    11.             {
    12.                 preferredSize.y = Mathf.FloorToInt((preferredSize.x / MAX_WIDTH)) * LINE_HEIGHT;
    13.                 preferredSize.x = MAX_WIDTH;
    14.             }
    15.             dialogText.rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, preferredSize.x);
    16.             dialogText.rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, preferredSize.y);
    17.  
    18.             int visibleCount = 0;
    19.             maxLength = inputText.Length;
    20.             while (visibleCount <= maxLength)
    21.             {
    22.                 dialogText.maxVisibleCharacters = ++visibleCount;
    23.                 yield return new WaitForSecondsRealtime(0.01f);
    24.             }
    25.             yield return null;
    26.         }
    EDIT: Ok, I clearly don't understand how GetPreferredValues works. If using the values as-is the preferred.y is always ~100px but after modifying the rect the preferred.y sometimes starts giving very high values. So I resorted using fixed LINE_HEIGHT
     
    Last edited: Jan 29, 2022
  30. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,836
    GetPreferredValues() has optional arguments where you can specify the maximum allowed width and height. If you specify a maximum width, then it will handle wrapping for you and increase the preferred height as necessary to fit within that width.
     
    spiritworld likes this.
  31. spiritworld

    spiritworld

    Joined:
    Nov 26, 2014
    Posts:
    29
    Thanks for the info. The documentation and the parameters should be named maxWidth/maxHeight instead of just width/height. At least for me this is very unclear :confused:
    "Function to Calculate the Preferred Width and Height of the text object given the provided width and height."
     
  32. victorchengthesecond

    victorchengthesecond

    Joined:
    Apr 9, 2018
    Posts:
    1
    upload_2022-5-29_23-15-43.png

    This worked for me
     
    sockpuppet likes this.
  33. SavedByZero

    SavedByZero

    Joined:
    May 23, 2013
    Posts:
    124
    The custom script in this page is great, but the padding only works on one side.
     
  34. SavedByZero

    SavedByZero

    Joined:
    May 23, 2013
    Posts:
    124
    Okay, tweaked it. 1) multiply the Padding by 2 everywhere you see it in the code.
    2) set the text rect transform’s localPosition to Padding.x,Padding.y inside those if (ResizeTextObject) statements.
    3) make sure your container graphic has its origin point as the upper left instead of the middle.
     
  35. huulong

    huulong

    Joined:
    Jul 1, 2013
    Posts:
    224
    Back to the general issue of Content Size Fitter, not specific to text:

    The suggestion works, but can lead to unexpected setup:
    - even if your parent has only one child, you need some Layout component to enable Content Size Fitter. There is no general "Layout" component so you really pick Horizontal or Vertical Layout just to make it work
    - if your child itself was using a Layout + Content Size Fitter to fit its own content, the child Content Size Fitter component will now display a warning "Parent has a type of layout group component. A child of a layout group should not have a Content Size Fitter component, since it should be driven by the layout group." but you really need the component, so just ignore the warning (there are other threads about this and come to the same conclusion, that it's a spurious warning is some cases)
     
  36. YuriPetskus

    YuriPetskus

    Joined:
    Jun 22, 2018
    Posts:
    18
    Width fitting with Font AutoSize works pretty bad.
    20-100% of the text length is added after the last character.
    TextMeshPro 3.0.6.
    Unity 2022.1.23f1
     
  37. EgoJacky

    EgoJacky

    Joined:
    Aug 15, 2017
    Posts:
    28
    I have just encountered the same problem as @YuriPetskus, does anyone have any ideas on how to solve it?
     
  38. krasner

    krasner

    Joined:
    Sep 9, 2014
    Posts:
    7
    With the script @Antistone originally posted, I had some issues with overflow text still being too big for my speech bubbles (which I'm updating dynamically). I got a more accurate result using
    Code (CSharp):
    1. LayoutUtility.GetPreferredHeight(Text.rectTransform)
    instead of Text.GetPreferredValues() in the update loop.

    I haven't tested any cases other than my own though. Hope this helps!