Search Unity

Limit Max Width of Layout Component?

Discussion in 'UGUI & TextMesh Pro' started by Stephan-B, Apr 5, 2015.

  1. Stephan-B

    Stephan-B

    Joined:
    Feb 23, 2011
    Posts:
    2,269
    Is there a way to set a Maximum width on a layout component? This would be useful to control how wide a given element can be to force word wrapping on text for instance.

    Example: Imagine a button with a child text component. The button width would increase with the length of the text up to this maximum value at which point the width would be fixed forcing the text to wrap to the next line now increasing the height of the button.

    In the same line, it would be nice to also be able to set a maximum value on the height.

    The Layout Element Component would seem to be the ideal candidate for this feature where in addition to min values, max values could be defined.
     
  2. crushhh

    crushhh

    Joined:
    Mar 22, 2015
    Posts:
    12
    I have to admit that I was a bit baffled that a max value didn't exist on the Layout Element component. It seems that Preferred seems to sort of work that way, but then - not really.
     
  3. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,689
    Nope @Stephan B There are no MAX values as you rightly point out.
    You'd have to create your own custom version for that.

    I'd also log it as a bug using the unity bug reporter in the editor as it does seem a very obvious gap.
     
    aesparza and geff like this.
  4. runevision

    runevision

    Joined:
    Nov 28, 2007
    Posts:
    1,892
    The preferred width/height property works as a max property if you have flexible width/height set to 0.

    If your element is inside a Horizontal Layout Group or Vertical Layout Group, make sure to disable "Child Force Expand" there as well.
     
  5. Stephan-B

    Stephan-B

    Joined:
    Feb 23, 2011
    Posts:
    2,269
    I still can't achieve the result that I seek using what you described. When using preferred width / height it does limit (max) the property but it also forces this as a minimum.

    For example assume you have a container for text that you wish to have a min height of 50 (assume 1 line of text) and max height of 150 (3 lines of text). So when the text is 1 line, the container is of height 50. When the text is 2 lines, the container is 100 and then when the text is 3 lines it is 150. When the text is 4 lines, the 4th line is truncated since the max height is 150. When using preferred height as you describe, the text container is always the preferred height and doesn't shrink in height based on the number of lines of text supplied.

    The parent of this text container has a ContentSizeFitter to make sure the height changes based on the lines of text. When using preferred height, the ContentSizeFitter simply uses that instead of the number of lines of text.
     
  6. Darkcoder

    Darkcoder

    Joined:
    Apr 13, 2011
    Posts:
    3,412
    Just came across this issue myself, most annoying!
     
  7. runevision

    runevision

    Joined:
    Nov 28, 2007
    Posts:
    1,892
    Hi, I'm just back from vacation, hence the late reply.

    After pondering the described use case for a bit, I don't think the layout system handles it well. I think OnGUI / IMGUI which it was inspired by also doesn't handle it well. (IMGUI has a MaxWidth property, but it's implemented very similar to the PreferredWidth in the new UI system - i.e if you specify a MaxWidth and there is sufficient space, it *will* be used.)

    To have support for a max-width that works in the way described in your post above, the layout calculations would have to be quite significantly different (and more sophisticated). I'll add it to our list of usability concerns.

    At this point it's not clear how many people are having major problems due to this. The UI Team is spread quite thin at the moment (I'm technically not on it myself anymore either), and while we're looking to expand it, this feature request is likely not going to be the first thing worked on. As usual, it can help us get a better overview of demand if you create an item for it on our feedback site and vote on it there.

    For anyone who might want to look into modifying the UI source code to add support for this, the relevant places to look are ILayoutElement.cs, LayoutUtility.cs, LayoutElement.cs, and HorizontalOrVerticalLayoutGroup.cs.
     
    _TheFuture_ and aesparza like this.
  8. Stephan-B

    Stephan-B

    Joined:
    Feb 23, 2011
    Posts:
    2,269
    Thank you for the reply and hope you had a great vacation.

    I'll look into the source code although modifying it for personal / project use is fine but in the context of releasing an Asset through the Unity Asset Store trickier to handle.
     
  9. BCook99

    BCook99

    Joined:
    Feb 17, 2015
    Posts:
    16
    I am running into this same issue, it is a real hindrance to the layout system when you are working with text controls that contain a lot of text. Really need max values support on LayoutElement.
     
    _TheFuture_ and ammirea like this.
  10. Stephan-B

    Stephan-B

    Joined:
    Feb 23, 2011
    Posts:
    2,269
    I have a lot of users still asking for this.
     
    _TheFuture_, ammirea and aesparza like this.
  11. kweinhold

    kweinhold

    Joined:
    Oct 18, 2015
    Posts:
    10
    I found a way that works for me. See if you can adapt it to your needs.

    I was working on a tool tip, so I have an image with text inside it. The tool tip should resize the width to fit the text, but have a max width, after which it will wrap and expand horizontally. To make it work, I added an empty object with a layout element with a preferred sized (the max width). I've attached an image to explain it in detail.

    1. empty object
    - Rect Transform
    - Layout Element: preferred width = 120
    - Content Size Fitter: Vertical Fit = Preferred Size
    - Vertical Layout Group: Child Force Expand = none checked

    2. Image
    - Rect Transform
    - Image
    - Vertical Layout Group: Child Force Expand = Height checked

    3. Text

     
    Zenov likes this.
  12. Stephan-B

    Stephan-B

    Joined:
    Feb 23, 2011
    Posts:
    2,269
    Using a similar setup with an Empty object as the top parent, I was able to get the desired result without the need to add a Layout Element on the top parent. Instead, the width of the RectTransform of this top parent serves as the width limit.

    As you can see in the video, the image (button in this case) will expand up to the width of the parent and the text will wrap as expected. Note that I am using TextMesh Pro here but the behavior is the same when using UI.Text.


    Click to view at full size.
     
    Last edited: Mar 25, 2016
  13. asperatology

    asperatology

    Joined:
    Mar 10, 2015
    Posts:
    981
    Video? What video?
     
  14. Stephan-B

    Stephan-B

    Joined:
    Feb 23, 2011
    Posts:
    2,269
    I meant animated GIF.
     
  15. asperatology

    asperatology

    Joined:
    Mar 10, 2015
    Posts:
    981
    I still don't see the GIF.
     
  16. Stephan-B

    Stephan-B

    Joined:
    Feb 23, 2011
    Posts:
    2,269
     
    Last edited: May 27, 2016
  17. hilllo

    hilllo

    Joined:
    May 27, 2016
    Posts:
    2

    It works INDEED! How do u come up with this idea?
     
    _TheFuture_ likes this.
  18. Stephan-B

    Stephan-B

    Joined:
    Feb 23, 2011
    Posts:
    2,269
    Credit for the idea goes to one of my TextMesh Pro users and @kweinhold who had very similar setups. I just figured out that it could be done without the extra layout element.
     
    _TheFuture_ likes this.
  19. ppnn13y

    ppnn13y

    Joined:
    Jan 21, 2017
    Posts:
    1
    It's not work. Could you upload the project, please!
     
  20. theRayll

    theRayll

    Joined:
    Mar 27, 2016
    Posts:
    19
    NOT working for me either.
     
  21. bkachmar

    bkachmar

    Joined:
    Mar 15, 2013
    Posts:
    43
    Thanks bro, this made my day =)
     
  22. smitchell

    smitchell

    Joined:
    Mar 12, 2012
    Posts:
    702
    yeah, I can't manage to get this working either :( anyone got a update on this?
     
  23. se210

    se210

    Joined:
    Jan 19, 2016
    Posts:
    3
    In the newer versions of Unity, it seems that you must check "Child Control Size" for both width and height in all the vertical/horizontal layout groups for this to work properly.
     
  24. mgstauff

    mgstauff

    Joined:
    Sep 6, 2017
    Posts:
    59
    Can Unity give an update on this issue? Or point to the feedback page/issue regarding this? Thanks
     
  25. dylanfries

    dylanfries

    Joined:
    Jul 11, 2012
    Posts:
    16
    I'd love to see a max as well as min size for text fields. I have ran into this issue on several projects already. It doesn't need to be complicated, just set a fixed max size that would then force the text BestFit setting to kick in. Is there any progress on this? Or a place I can vote for it? Its a common issue.
     
  26. ugotworms

    ugotworms

    Joined:
    Jul 16, 2017
    Posts:
    1
    This issue is plaguing my project as well, which is meant to become an asset store package, so it must work out of the box. Being able to set a fixed max width/height would be great.
     
  27. JDRenais

    JDRenais

    Joined:
    Sep 24, 2018
    Posts:
    1
    If anyone is still having this issue, I did as Stephan mentioned in the GUI then anchored the empty object to the corner of my screen. Now it stays the correct size and keeps to its proper corner as well. Hope this helps anyone having trouble.
     
  28. Leslie-Young

    Leslie-Young

    Joined:
    Dec 24, 2008
    Posts:
    1,148
    Here is the setup which worked for me. The Label does not seem to need a ContentSizeFitter. Change Child Align for your needs. I needed my label aligned in center in this case. I have a background that must scale with the text, that is why there is an Image on "Expander". You can remove this if not needed.
     

    Attached Files:

    Dacusx, SlowSeer, Neil-Corre and 7 others like this.
  29. devjah

    devjah

    Joined:
    Dec 8, 2014
    Posts:
    2
    It's 2019 and still no max value field
     
  30. EvanVirbela

    EvanVirbela

    Joined:
    Apr 15, 2019
    Posts:
    2
    I agree, this is ridiculous, it should have a max value. Anyways here is a stack overflow of a possible solution. I hope this helps.

    stackoverflow.com/questions/33486959/unity-cant-find-ui-layout-element-max-size
     
    Anastasabal, sinedsem, Masea1 and 2 others like this.
  31. OrokanaAi

    OrokanaAi

    Joined:
    Mar 20, 2019
    Posts:
    1
    Been struggling with this for days. Trying to create a modal that has dynamically fitted text content and doesn't exceed the screen bounds. Once hitting the min/max it needs to scroll.

    This would take 2 minutes in a web app. Instead of have to read a bunch of documentation and scour the internet for a solution. At this point it's probably easier to create a script that resizes the element if it's outside the window bounds. Good grief.
     
  32. NobleRobot

    NobleRobot

    Joined:
    Jan 14, 2016
    Posts:
    56
    I, too have been struggling with this. Existing solves using nested objects are not feasible for me since each item I need to respect a "MaxWidth" is part of a horizontal layout group, so I can't allow a dummy parent object to remain wider than my max width.

    So, I came up with a little hack using LayoutElement. Here's a snippet:

    Code (CSharp):
    1. public class MaxWidth : MonoBehaviour {
    2.  
    3.     public RectTransform textTransform;
    4.     public LayoutElement layoutElement;
    5.  
    6.     private void Start(){
    7.         checkWidth();
    8.     }
    9.  
    10.     public void checkWidth(){
    11.         layoutElement.enabled = (textTransform.rect.width > layoutElement.preferredWidth);
    12.     }
    13.  
    14. }
    My actual code is a little more complex, because of LayoutRebuilder nonsense meaning I need to delay the check for one frame, but basically, the heart of it is a simple check which only enables the LayoutElement component if the width exceeds its preferredWidth, otherwise it stays disabled and thus preferredWidth doesn't act as a minimum width.

    I make this check on Start, as well as when my game's language changes, although if you're using an input field or other scenario you'll have to make sure it gets checked any other time that the text is changed (just don't put it in Update unless you can afford to be lazy).

    That this is such an incredibly simple solve makes me a little incredulous that the UI team can't just add something like this to the LayoutElement component after 4+ years of requests.



    Above is the result as seen in my game's main menu (the button prompts at the bottom). There are plenty of use cases for a "max width", but surely I'm not the only one who needs it for exactly this purpose, right?
     
    Last edited: Nov 1, 2019
  33. Tymianek

    Tymianek

    Joined:
    May 16, 2015
    Posts:
    97
    The most reliable way to this for me is using a custom script with the SetSizeWithCurrentAnchors method of a RectTransform.
    The simplest way is to just put in the Update method of a MonoBehaviour.
    (You could also override the Unity's built in layout methods. I have insufficient knowledge to explain it well.)
    Code (CSharp):
    1.    
    2.     void Update()
    3.     {
    4.         rect.SetSizeWithCurrentAnchors(
    5.             RectTransform.Axis.Horizontal,
    6.             width // calculate width to your needs
    7.         );
    8.     }
    9.  
    To ensure your new width is within the parent you can use a Min or a Clamp method.
    To ensure it works during editing and not just in play mode, you can add [ExecuteInEditMode] tag.

    One of the simple layout scripts I use below. You may need to reopen the scene after adding the script for the Awake() method to trigger.
    Code (CSharp):
    1.  
    2. using UnityEngine;
    3.  
    4. [ExecuteInEditMode]
    5. public class ExpandWidthToMaxLayout: MonoBehaviour
    6. {
    7.     RectTransform rect;
    8.     RectTransform parentRect;
    9.     [SerializeField] float maxWidth;
    10.  
    11.     private void Awake()
    12.     {
    13.         rect = GetComponent<RectTransform>();
    14.         parentRect = transform.parent.GetComponent<RectTransform>();
    15.     }
    16.  
    17.     void Update()
    18.     {
    19.         rect.SetSizeWithCurrentAnchors(
    20.             RectTransform.Axis.Horizontal,
    21.             Mathf.Min(parentRect.rect.width, maxWidth)
    22.         );
    23.     }
    24. }
    25.  
     
  34. AdrienMgm

    AdrienMgm

    Joined:
    Oct 10, 2017
    Posts:
    4
    It's 2020 and still no max value field
     
    Yany, Neil-Corre, ing_unity and 7 others like this.
  35. tjPark

    tjPark

    Joined:
    Mar 19, 2015
    Posts:
    13

    for buddies like me, hope this sentence be bolded and colored
     
  36. robal1991

    robal1991

    Joined:
    Mar 31, 2016
    Posts:
    33
    @runevision
    Is it possible to have content size fitter with preferred vertical size to make it expand up to certain point and to use scroll view if there is more content?

    Example:
    I have a panel with a text field. Text might be very short or very long.
    1. I want the panel minimum height to be 200 (this is working ok with layout element min size = 200).
    2. I want the content size fitter to expand it up to 450 if needed (working ok with vertical fit = preferred size)
    3. But if even more content size is needed the panel should remain with height = 450 and there should be scroll view showing the scroll bar. The text should be scrollable with scroll view.

    EDIT: My setup is:
    Panel min size 650x200, max size 650x450, white background. Panel is vertical layout and contains:
    1. Title height 30 always visible
    2. Message which can contain very short or very long text. Text is scrollable, scroll bar appears on the right side of the panel.
    3. Buttons panel always visible.

    Min size example:
    upload_2020-3-2_11-58-48.png

    Max size example:
    upload_2020-3-2_12-0-18.png

    Example with scroll rect (mounted manually, I don't know how to make Title and Buttons not scrolling with text):
    upload_2020-3-2_12-4-20.png


    Thanks for help
     
    Last edited: Mar 2, 2020
    suiboli314, Noxalus and MaskedMouse like this.
  37. MaskedMouse

    MaskedMouse

    Joined:
    Jul 8, 2014
    Posts:
    1,092
    I've been trying to do the exact same thing. Still haven't found a solution for it after all these years. Other than listening to the size changes and if / when it reaches a certain limit then disable expansion.
    On a sidenote, with UI Elements it's going to be a lot easier to setup as it has a maximum size limit property! But that's still under development for runtime UI. I'm looking forward to the release of runtime UI Elements for camera space (or overlay when AA is supported for SVG) & world space with data binding. But that'll probably be the end of this year.
     
  38. olejuer

    olejuer

    Joined:
    Dec 1, 2014
    Posts:
    211
    I want to bump this.
    Yes, it is possible to have a parent Rect work as a container to set boundaries. However, this results in the Canvas always being at maximum width. In my case, I have a background image on the top-level of my canvas hierarchy which is supposed to scale with the children.
    The suggested scripts do work but mark the Canvas dirty every frame which is a significant performance penalty. On mobile hardware this can be unfeasible. So I have to write a util that triggers the respective resize function whenever necessary. This is uncomfortable to work with in the editor and a possible source for bugs.
    Any chance for a better solution? I really don't have the expertise with the UI system to figure it out.
     
  39. ehudcandivore

    ehudcandivore

    Joined:
    Jul 18, 2018
    Posts:
    12
    Things like this make me sad. 5 years in and Unity still hasn't added this simple feature...
     
    anisimovdev and Foreman_Dev like this.
  40. MaskedMouse

    MaskedMouse

    Joined:
    Jul 8, 2014
    Posts:
    1,092
    Development is in UI Toolkit, but it’ll take a long while before that’s on par with Unity UI
     
  41. krides

    krides

    Joined:
    Apr 7, 2011
    Posts:
    38
    Hi! I am probably going to hack something together with parenting like suggested above, but in my case, it means rebuilding my whole (already rather complicated) UI hierarchy containing ScrollViews, masks, and various layout groups. There should really be an option for Max height/width.

    Edit: Actually, couldn't figure out how to set up the hierarchy to make it work. Definitely cannot replicate any of the results shown above. Instead, I wrote this simple script that does just the resizing and nothing else:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.UI;
    5.  
    6. [ExecuteInEditMode]
    7. public class ResizeRectBasedOnHDelta : MonoBehaviour
    8. {
    9.     public float maxHeight = 580f;
    10.  
    11.     public RectTransform rtChild;
    12.     public RectTransform rtParent;
    13.  
    14.     void Update()
    15.     {
    16.         rtParent.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, Mathf.Min(maxHeight, rtChild.rect.height));
    17.     }
    18. }
     
    Last edited: Aug 9, 2020
  42. MaskedMouse

    MaskedMouse

    Joined:
    Jul 8, 2014
    Posts:
    1,092
    Wouldn't this cause a rebuild of the UI every frame since you're setting the size every frame?
    That would be bad for performance (depending on the UI, but still bad for performance if it does dirty the canvas every frame due to this).

    You could also create a script inheriting from UIBehaviour and use OnRectTransformDimensionsChange
    Check the size of the panel and if it is above the limit disable the content size fitter.
    Or have a child with OnRectTransformDimensionChange to call the parent to check whether it should enable or disable content size fitting or not.
     
    olejuer likes this.
  43. krides

    krides

    Joined:
    Apr 7, 2011
    Posts:
    38
    I like the idea of detecting a dimensions change, but I can't figure out how to use OnRectTransformDimensionsChange. It doesn't appear to be one of Unity's magic functions like Update or a delegate. Do you think you could give an example, @MaskedMouse?

    Update: I tried doing this on the child, but it crashes Unity every time.

    Code (CSharp):
    1. [ExecuteInEditMode]
    2. public class ResizeRectBasedOnHDelta : UnityEngine.EventSystems.UIBehaviour
    3. {
    4.     public float maxHeight = 580f;
    5.  
    6.     public RectTransform rtChild;
    7.     public RectTransform rtParent;
    8.  
    9.     override protected void OnRectTransformDimensionsChange()
    10.     {
    11.         rtParent.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, Mathf.Min(maxHeight, rtChild.rect.height));
    12.     }
    13. }
     
    Last edited: Aug 15, 2020
  44. MaskedMouse

    MaskedMouse

    Joined:
    Jul 8, 2014
    Posts:
    1,092
    Because you're introducing a loop now.
    OnRectTransformDimensionsChange is called when the width / height of a rect transform changes. i.e. when your children are expanding this method will be called.
    Since you're setting the size with SetSizeWithCurrentAnchors, you're causing a change in width and height and thus call OnRectTransformDimensionsChange again. It is supposed to be a event based call (more efficient) where you check whether the width / height is higher than a maximum. If so then stop expanding by disabling the Content Size Fitter. Not to set the size again.

    I'm usually focussed on performance of UI. Since UGUI isn't the best in performance to start with. With larger UI setups things tend to get slow rather quickly.

    The problem with this script is, when it has reached the maximum it won't shrink back.

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.EventSystems;
    3. using UnityEngine.UI;
    4.  
    5. namespace UIExtensions
    6. {
    7.     [ExecuteInEditMode]
    8.     [RequireComponent(typeof(RectTransform), typeof(ContentSizeFitter))]
    9.     public class MaxExpandableRect : UIBehaviour
    10.     {
    11.         public Vector2 MaximumSize;
    12.         public ContentSizeFitter SizeFitter;
    13.    
    14.         protected override void OnRectTransformDimensionsChange()
    15.         {
    16.             var rectTransform = (RectTransform) transform;
    17.  
    18.             // If it reached the maximum size Horizontally
    19.             if (rectTransform.sizeDelta.x > MaximumSize.x)
    20.             {
    21.                 SizeFitter.horizontalFit = ContentSizeFitter.FitMode.Unconstrained;
    22.                 rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, MaximumSize.x);          
    23.             }
    24.        
    25.             // If it reached the maximum size Vertically
    26.             if (rectTransform.sizeDelta.y > MaximumSize.y)
    27.             {
    28.                 SizeFitter.verticalFit = ContentSizeFitter.FitMode.Unconstrained;
    29.                 rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, MaximumSize.y);
    30.             }
    31.         }
    32.     }
    33. }
    Then another solution would be creating your own ContentSizeFitter ofcourse. Because the problem is the content size fitter itself not defining a maximum size. Only a preferred size.

    A simple copy pasta of the original ContentSizeFitter renamed to Extension.
    I've changed it up a bit conform coding standards that I hold myself to.
    I've removed the
    SetPropertyUtility.SetStruct
    calls because they are internal methods they can't be used. But afaik all it does is checking if the enum has changed or not. If it has, set it dirty.
    Then when the HandleSelfFittingAlongAxis is being called, clamp the value of the preferred maximum size.

    edit: noticed a few refactoring errors.
    Code (CSharp):
    1. using UnityEngine.EventSystems;
    2.  
    3. namespace UnityEngine.UI
    4. {
    5.     [AddComponentMenu("Layout/Content Size Fitter With Max", 141)]
    6.     [ExecuteAlways]
    7.     [RequireComponent(typeof(RectTransform))]
    8.     public class ContentSizeFitterExtension : UIBehaviour, ILayoutSelfController
    9.     {
    10.         /// <summary>
    11.         /// The size fit modes avaliable to use.
    12.         /// </summary>
    13.         public enum FitMode
    14.         {
    15.             /// <summary>
    16.             /// Don't perform any resizing.
    17.             /// </summary>
    18.             Unconstrained,
    19.  
    20.             /// <summary>
    21.             /// Resize to the minimum size of the content.
    22.             /// </summary>
    23.             MinSize,
    24.  
    25.             /// <summary>
    26.             /// Resize to the preferred size of the content.
    27.             /// </summary>
    28.             PreferredSize
    29.         }
    30.  
    31.         [SerializeField]
    32.         protected FitMode horizontalFit = FitMode.Unconstrained;
    33.  
    34.         /// <summary>
    35.         /// The fit mode to use to determine the width.
    36.         /// </summary>
    37.         public FitMode HorizontalFit
    38.         {
    39.             get => horizontalFit;
    40.             set
    41.             {
    42.                 if (horizontalFit == value) return;
    43.                 horizontalFit = value;
    44.                 SetDirty();
    45.             }
    46.         }
    47.  
    48.         [SerializeField]
    49.         protected FitMode verticalFit = FitMode.Unconstrained;
    50.  
    51.         /// <summary>
    52.         /// The fit mode to use to determine the height.
    53.         /// </summary>
    54.         public FitMode VerticalFit
    55.         {
    56.             get => verticalFit;
    57.             set
    58.             {
    59.                 if (verticalFit == value) return;
    60.                 verticalFit = value;
    61.                 SetDirty();
    62.             }
    63.         }
    64.  
    65.         [Tooltip("Maximum Preferred size when using Preferred Size")]
    66.         public Vector2 MaximumPreferredSize;
    67.  
    68.         [System.NonSerialized]
    69.         private RectTransform rectTransform;
    70.  
    71.         private RectTransform RectTransform
    72.         {
    73.             get
    74.             {
    75.                 if (rectTransform == null)
    76.                     rectTransform = GetComponent<RectTransform>();
    77.                 return rectTransform;
    78.             }
    79.         }
    80.  
    81.         private DrivenRectTransformTracker tracker;
    82.  
    83.         protected override void OnEnable()
    84.         {
    85.             base.OnEnable();
    86.             SetDirty();
    87.         }
    88.  
    89.         protected override void OnDisable()
    90.         {
    91.             tracker.Clear();
    92.             LayoutRebuilder.MarkLayoutForRebuild(RectTransform);
    93.             base.OnDisable();
    94.         }
    95.  
    96.         protected override void OnRectTransformDimensionsChange()
    97.         {
    98.             SetDirty();
    99.         }
    100.  
    101.         private void HandleSelfFittingAlongAxis(int axis)
    102.         {
    103.             var fitting = (axis == 0 ? HorizontalFit : VerticalFit);
    104.             if (fitting == FitMode.Unconstrained)
    105.             {
    106.                 // Keep a reference to the tracked transform, but don't control its properties:
    107.                 tracker.Add(this, RectTransform, DrivenTransformProperties.None);
    108.                 return;
    109.             }
    110.  
    111.             tracker.Add(this, RectTransform, (axis == 0 ? DrivenTransformProperties.SizeDeltaX : DrivenTransformProperties.SizeDeltaY));
    112.  
    113.             switch (fitting)
    114.             {
    115.                 // Set size to min or preferred size
    116.                 case FitMode.MinSize:
    117.                     RectTransform.SetSizeWithCurrentAnchors((RectTransform.Axis) axis, LayoutUtility.GetMinSize(rectTransform, axis));
    118.                     break;
    119.  
    120.                 case FitMode.PreferredSize:
    121.                     RectTransform.SetSizeWithCurrentAnchors((RectTransform.Axis) axis, Mathf.Clamp(LayoutUtility.GetPreferredSize(rectTransform, axis), 0, axis == 0 ? MaximumPreferredSize.x : MaximumPreferredSize.y));
    122.                     break;
    123.             }
    124.         }
    125.  
    126.         /// <summary>
    127.         /// Calculate and apply the horizontal component of the size to the RectTransform
    128.         /// </summary>
    129.         public virtual void SetLayoutHorizontal()
    130.         {
    131.             tracker.Clear();
    132.             HandleSelfFittingAlongAxis(0);
    133.         }
    134.  
    135.         /// <summary>
    136.         /// Calculate and apply the vertical component of the size to the RectTransform
    137.         /// </summary>
    138.         public virtual void SetLayoutVertical()
    139.         {
    140.             HandleSelfFittingAlongAxis(1);
    141.         }
    142.  
    143.         protected void SetDirty()
    144.         {
    145.             if (!IsActive())
    146.                 return;
    147.  
    148.             LayoutRebuilder.MarkLayoutForRebuild(RectTransform);
    149.         }
    150.  
    151.     #if UNITY_EDITOR
    152.         protected override void OnValidate()
    153.         {
    154.             SetDirty();
    155.         }
    156.  
    157.     #endif
    158.     }
    159. }
     
    Last edited: Aug 15, 2020
    krides likes this.
  45. Nodrap

    Nodrap

    Joined:
    Nov 4, 2011
    Posts:
    83
    But PLEASE, never put a tutorial video into a gif - you can't pause or rewind it!!!
     
    Neil-Corre, pixelR and Reahreic like this.
  46. LazloBonin

    LazloBonin

    Joined:
    Mar 6, 2015
    Posts:
    812
    Alright, let's not get to 2021.

    It's actually a very simple fix.

    The solution is to create your own content size fitter class by extending the existing one, except your custom class has a max width and height.

    You need two files: one for the component itself, and another for its editor.

    I called mine
    ContentSizeFitterWithMax
    , but pick whichever name you like.

    ContentSizeFitterWithMax.cs:

    Code (CSharp):
    1. using System;
    2.  
    3. namespace UnityEngine.UI
    4. {
    5.     [AddComponentMenu("Layout/Content Size Fitter With Max", 141)]
    6.     [ExecuteAlways]
    7.     [RequireComponent(typeof(RectTransform))]
    8.     public class ContentSizeFitterWithMax : ContentSizeFitter
    9.     {
    10.         [NonSerialized]
    11.         private RectTransform m_Rect;
    12.  
    13.         private RectTransform rectTransform
    14.         {
    15.             get
    16.             {
    17.                 if (m_Rect == null)
    18.                 {
    19.                     m_Rect = GetComponent<RectTransform>();
    20.                 }
    21.  
    22.                 return m_Rect;
    23.             }
    24.         }
    25.  
    26.         [SerializeField]
    27.         private float m_MaxWidth = -1;
    28.  
    29.         public float maxWidth
    30.         {
    31.             get => m_MaxWidth;
    32.             set => m_MaxWidth = value;
    33.         }
    34.  
    35.         [SerializeField]
    36.         private float m_MaxHeight = -1;
    37.  
    38.         public float maxHeight
    39.         {
    40.             get => m_MaxHeight;
    41.             set => m_MaxHeight = value;
    42.         }
    43.  
    44.         public override void SetLayoutHorizontal()
    45.         {
    46.             base.SetLayoutHorizontal();
    47.  
    48.             if (maxWidth > 0)
    49.             {
    50.                 if (horizontalFit == FitMode.MinSize)
    51.                 {
    52.                     rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, Mathf.Min(LayoutUtility.GetMinSize(m_Rect, 0), maxWidth));
    53.                 }
    54.                 else if (horizontalFit == FitMode.PreferredSize)
    55.                 {
    56.                     rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, Mathf.Min(LayoutUtility.GetPreferredSize(m_Rect, 0), maxWidth));
    57.                 }
    58.             }
    59.         }
    60.  
    61.         public override void SetLayoutVertical()
    62.         {
    63.             base.SetLayoutVertical();
    64.  
    65.             if (maxHeight > 0)
    66.             {
    67.                 if (verticalFit == FitMode.MinSize)
    68.                 {
    69.                     rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, Mathf.Min(LayoutUtility.GetMinSize(m_Rect, 1), maxHeight));
    70.                 }
    71.                 else if (verticalFit == FitMode.PreferredSize)
    72.                 {
    73.                     rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, Mathf.Min(LayoutUtility.GetPreferredSize(m_Rect, 1), maxHeight));
    74.                 }
    75.             }
    76.         }
    77.     }
    78. }
    ContentSizeFitterWithMaxEditor.cs:

    Code (CSharp):
    1. using UnityEngine.UI;
    2.  
    3. namespace UnityEditor.UI
    4. {
    5.     [CustomEditor(typeof(ContentSizeFitterWithMax), true)]
    6.     [CanEditMultipleObjects]
    7.     public class ContentSizeFitterWithMaxEditor : ContentSizeFitterEditor
    8.     {
    9.         SerializedProperty m_MaxWidth;
    10.  
    11.         SerializedProperty m_MaxHeight;
    12.  
    13.         protected override void OnEnable()
    14.         {
    15.             base.OnEnable();
    16.             m_MaxWidth = serializedObject.FindProperty("m_MaxWidth");
    17.             m_MaxHeight = serializedObject.FindProperty("m_MaxHeight");
    18.         }
    19.  
    20.         public override void OnInspectorGUI()
    21.         {
    22.             serializedObject.Update();
    23.             EditorGUILayout.PropertyField(m_MaxWidth, true);
    24.             EditorGUILayout.PropertyField(m_MaxHeight, true);
    25.             serializedObject.ApplyModifiedProperties();
    26.  
    27.             base.OnInspectorGUI();
    28.         }
    29.     }
    30. }
    Proof that it works: I set my max width to 400 on this dialogue choice text, so short choices will expand, but longer choices will wrap, exactly like requested by OP.

     
    njellingson, ookk47oo, MUGIK and 20 others like this.
  47. diesoftgames

    diesoftgames

    Joined:
    Nov 27, 2018
    Posts:
    122
    Unfortunately...
    Using the content size fitter is nice, but once you're already working with a Layout Group, you can't use a content size fitter without potentially running into issues. I'm bumping into this once again with my UI, this time more specifically I just want to limit the width of a text field that is long, but I also don't want it to force the parent layout group to be wider than it needs to be. The only way this works is if there is some max width solution that works within a layout group. I'm fiddling around with it, and I'll try to remember to come back and follow up if I come up with a decent workaround.

    Update:
    I did get something working for my needs. It was kind of particular though so I'll explain the scenario:
    I have a horizontally nesting menu that scrolls vertically, but each menu is sized horizontally according to its widest layout item. This is making use of nested horizontal and vertical layout groups, with a large flexible width for the empty area that essentially pushes the menus down to the smallest width they can be without truncating text.
    BUT there are some text labels I want to wrap when they get a certain width, but if the preferred size is smaller we should use that (so we don't unnecessary increase the width of the menu), but the width can also increase up to the width of the parent RectTransform's width (so that we aren't awkwardly word wrapping to prevent from making the menu wider than it needs to be if the menu is already fairly wide). It's a little awkward, so perhaps a screenshot will better explain:

    The small explainer text is the bit in question. You can see how it wants to at least be a little bigger than the contents of the menu would be without it, but if left to expand as much as it would like, it would cause that menu to take up the entirety of the width left. So what I ended up doing was creating a LayoutGroup for it and attached this UIBehaviour I made based on the default implementation of LayoutElement:

    Code (CSharp):
    1.  
    2.     [ExecuteInEditMode]
    3.     [RequireComponent(typeof(RectTransform))]
    4.     public class MaxWidthLayoutElement : UIBehaviour, ILayoutElement {
    5.         private float m_PreferredWidth;
    6.      
    7.         public float MaxForcedWidth;
    8.         public LayoutGroup LayoutGroup;
    9.  
    10.         public virtual float minWidth => -1;
    11.         public virtual float minHeight => -1;
    12.         public virtual float preferredWidth => m_PreferredWidth;
    13.         public virtual float preferredHeight => -1;
    14.         public virtual float flexibleWidth => -1;
    15.         public virtual float flexibleHeight => -1;
    16.         public virtual int layoutPriority => 1;
    17.  
    18.         public virtual void CalculateLayoutInputHorizontal() { }
    19.         public virtual void CalculateLayoutInputVertical() { }
    20.  
    21.         #region Unity Lifetime calls
    22.  
    23.         protected override void OnEnable() {
    24.             base.OnEnable();
    25.             SetDirty();
    26.         }
    27.  
    28.         protected override void OnTransformParentChanged() {
    29.             LayoutGroup.CalculateLayoutInputHorizontal();
    30.             var preferredWidth = LayoutGroup.preferredWidth;
    31.             var parentWidth = ((RectTransform)transform.parent).rect.width;
    32.             m_PreferredWidth = math.max(parentWidth, math.min(preferredWidth, MaxForcedWidth));
    33.             SetDirty();
    34.         }
    35.  
    36.         protected override void OnDisable() {
    37.             SetDirty();
    38.             base.OnDisable();
    39.         }
    40.  
    41.         protected override void OnDidApplyAnimationProperties() {
    42.             SetDirty();
    43.         }
    44.  
    45.         protected override void OnBeforeTransformParentChanged() {
    46.             SetDirty();
    47.         }
    48.  
    49.         #endregion
    50.  
    51.         protected void SetDirty() {
    52.             if (!IsActive()) { return; }
    53.             LayoutRebuilder.MarkLayoutForRebuild(transform as RectTransform);
    54.         }
    55.  
    56.         #if UNITY_EDITOR
    57.         protected override void OnValidate() {
    58.             SetDirty();
    59.         }
    60.  
    61.         #endif
    62.     }
    63.  
    It feels a little awkward, but it seems to work nicely in all use cases.
     
    Last edited: Feb 14, 2021
  48. codestage

    codestage

    Joined:
    Jul 27, 2012
    Posts:
    1,931
    It's 2021 and still no easy way to do this, but @Stephan-B's post saves another day, this is a working solution, thank you!
     
  49. suiboli314

    suiboli314

    Joined:
    Apr 8, 2020
    Posts:
    9
    I managed to make this.

    A background picture as parent, with layout group controlling child size, and thanks to @Ludiq, his
    Background panel.png

    A ScrollRect, as one of children of background pic, with one layout group controlling child size. I only set Content and Permanent Scrollbar for Scroll Rect. Please do not assign Viewport.
    ScrollRect.png

    A mask as viewport. But not assigned in ScrollRect. With Mask for world space UI, or Rect Mask 2D for Overlay/Screen Space, and image required by mask. Layout Element. A Script to get preferred layout width and height.
    ViewPort.png ViewPort-Maxed.png
    SimpleGetPreferredChildLayout.cs
    Code (CSharp):
    1. using TMPro;
    2. using UnityEngine;
    3. using UnityEngine.EventSystems;
    4. using UnityEngine.UI;
    5.  
    6. [RequireComponent(typeof(LayoutElement))]
    7. [ExecuteAlways]
    8. public class SimpleGetPreferredChildLayout : UIBehaviour
    9. {
    10.     [SerializeField]
    11.     private LayoutElement m_layout;
    12.  
    13.     private LayoutElement Layout
    14.     {
    15.         get
    16.         {
    17.             if (m_layout == null) m_layout = GetComponent<LayoutElement>();
    18.             return m_layout;
    19.         }
    20.     }
    21.  
    22.     [SerializeField]
    23.     private TMP_Text m_TextComponent;
    24.  
    25.     private TMP_Text TextComponent
    26.     {
    27.         get
    28.         {
    29.             if (m_TextComponent == null) m_TextComponent = GetComponentInChildren<TMP_Text>();
    30.             return m_TextComponent;
    31.         }
    32.     }
    33.  
    34.     private RectTransform m_textRect;
    35.  
    36.     private RectTransform TextRect
    37.     {
    38.         get
    39.         {
    40.             if (m_textRect == null) m_textRect = TextComponent.GetComponent<RectTransform>();
    41.             return m_textRect;
    42.         }
    43.     }
    44.  
    45.     protected override void OnEnable()
    46.     {
    47.         base.OnEnable();
    48.         // Subscribe to event fired when text object has been regenerated.
    49.         TMPro_EventManager.TEXT_CHANGED_EVENT.Add(ON_TEXT_CHANGED);
    50.     }
    51.  
    52.     protected override void OnDisable()
    53.     {
    54.         base.OnDisable();
    55.         TMPro_EventManager.TEXT_CHANGED_EVENT.Remove(ON_TEXT_CHANGED);
    56.     }
    57.  
    58.     protected void ON_TEXT_CHANGED(Object obj)
    59.     {
    60.         if (obj == TextComponent)
    61.             OnRectTransformDimensionsChange();
    62.     }
    63.  
    64.     protected override void OnRectTransformDimensionsChange()
    65.     {
    66.         base.OnRectTransformDimensionsChange();
    67.  
    68.         if (transform.childCount != 1) return;
    69.         Layout.preferredHeight = LayoutUtility.GetPreferredHeight(TextRect);
    70.         Layout.preferredWidth = LayoutUtility.GetPreferredWidth(TextRect);
    71.     }
    72. }
    A Text Mesh UGUI as content with basic content size fitter, Vertical Fit set to Preferred Size. RectTransform's anchor stretches on the top.
    Content.png
     
    Last edited: Jun 8, 2021
  50. bilalakil

    bilalakil

    Joined:
    Jan 28, 2018
    Posts:
    77
    Hey all, just wanted to share my solution. I'm quite happy with its simplicity, and it also works in edit mode. Add this component beside your `ContentSizeFitter` + layout element or group (e.g. `VerticalLayoutGroup`):

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.UI;
    3.  
    4. [ExecuteAlways]
    5. [RequireComponent(typeof(ContentSizeFitter))]
    6. public class ContentSizeFitterMaxWidth : MonoBehaviour
    7. {
    8.     public float maxWidth;
    9.  
    10.     RectTransform _rtfm;
    11.     ContentSizeFitter _fitter;
    12.     ILayoutElement _layout;
    13.  
    14.     void OnEnable()
    15.     {
    16.         _rtfm = (RectTransform)transform;
    17.         _fitter = GetComponent<ContentSizeFitter>();
    18.         _layout = GetComponent<ILayoutElement>();
    19.     }
    20.  
    21.     void Update()
    22.     {
    23.         _fitter.horizontalFit = _layout.preferredWidth > maxWidth
    24.             ? ContentSizeFitter.FitMode.Unconstrained
    25.             : ContentSizeFitter.FitMode.PreferredSize;
    26.        
    27.         if (_layout.preferredWidth > maxWidth)
    28.         {
    29.             _fitter.horizontalFit = ContentSizeFitter.FitMode.Unconstrained;
    30.             _rtfm.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, maxWidth);
    31.         }
    32.         else
    33.             _fitter.horizontalFit = ContentSizeFitter.FitMode.PreferredSize;
    34.     }
    35.  
    36.     void OnValidate() => OnEnable();
    37. }
    38.  
     
    Wolfram likes this.