Suggestion for ContentSizeFitter Script

So far, this is an amazing script that does exactly what I need it to, except for when I’m using a sprite that’s larger than my average use case. My current project is incredibly simple, so I won’t be using even a fraction of the GPU memory or memory bandwidth, so I see no reason to not use larger-than-necessary sprites when that also allows me to do some things like pretty antialiasing.

Unfortuantely, the ContentSizeFitter script on “Preferred Size” seems to always zoom the sprite out to its natural size. For example, if I have text on a button, and I want ContentSizeFitter to size itself to the text plus margin, it will instead blow up to the sprite size. This happens also with a 9-sliced image: It will correctly reduce the size of the top/bottom/left/right stretchy elements to zero, but it will not then reduce the size further and instead the four corners end up being the preferred size.

It would be nice to have a checkbox or something in order to say “I want the child objects to determine the size of this UI element, and completely ignore this UI element itself” so it will fit itself to its content regardless of its own structure.

Hi, so you could modify this yourself.

Here is the code for our current ContentSizeFitter:

Feel free to take this and modify it to add your desired functionality, this is because right now we feel that we want the ContentSizeFitter to always pick the default element size without adding special corner cases.

We would prefer to keep the UI system from becoming overly complex, so if we can solve the use case without introducing additional settings, that would be best.

I think you might be able to work around this by making the Image a child of the Game Object with the Content Size Fitter on it, and make it stretch to the full size of its parent. Also add a LayoutElement with the Ignore Layout setting turned on on the Image. I think this approach should work with no coding needed.

Tim’s suggestion of making your own script to replace Content Size Fitter is also an option. It could be programmed to look at the preferred size of a specific component instead of considering all components (which is what happens when using the methods in the LayoutUtility class).

That was a good thought, but it doesn’t seem to work: If I put a ContentSizeFitter on an empty game object with the image as a child, it forces the empty game object to have width/height of 0 (ContentSizeFitter on Preferred) and doesn’t do anything to the image or the text. Same problem with trying IgnoreLayout on the image but the text being normal: An empty game object and ContentSizeFitter just don’t work together somehow.

(I just tried putting the image as a child of the text and the ContentSizeFitter on it, which obviously makes them draw in the wrong order, but also made the image zoom out to full size again. Ignore Layout on the image at that point just translated it based on its anchor and leaves it huge.

Okay, I did a simple test: With the text set to anchor to center/middle and position 0,0, I added this script to the image:

public class Test : MonoBehaviour
{
    void Start ( )
    {
        RectTransform bar = GetComponent < RectTransform > ( ) ;
        RectTransform baz = transform . GetChild ( 0 ) . GetComponent < RectTransform > ( ) ;
        bar . sizeDelta = new Vector2 ( LayoutUtility . GetPreferredWidth ( baz ) , LayoutUtility . GetPreferredHeight ( baz ) ) ;
        baz . sizeDelta = bar . sizeDelta ;
    }
}

And it works perfectly in my specific case. I can see how it would be incredibly difficult to generalize such a thing, though, so I will just go with this for my project’s buttons. Now I just have to figure out the ILayoutGroup bits so I can get this to work dynamically and in the editor, and it will be golden.

Here’s my completed script: It will change the width and height of the object the script is on to be the maximum of the Preferred widths and heights of all of its immediate children (which works best if all those children are anchored center/middle at 0,0), and it allows the specification of horizontal and vertical padding. Anyone see any mistakes or blunders? (Yes, I know my spacing style is weird.)

using UnityEngine ;
using UnityEngine . UI ;
using UnityEngine . EventSystems ;

[ExecuteInEditMode]
[RequireComponent ( typeof ( RectTransform ) ) ]
public class FitParentToChildren : UIBehaviour , ILayoutSelfController
{
    public float HorizontalPadding ;
    public float VerticalPadding ;

    DrivenRectTransformTracker DrivenTransformTracker ;

    RectTransform _DrivenTransform ;
    RectTransform DrivenTransform
    {
        get
        {
            if ( _DrivenTransform == null )
            {
                _DrivenTransform = GetComponent < RectTransform > ( ) ;
            }

            return ( _DrivenTransform ) ;
        }
    }

    void SetDirty ( )
    {
        if ( ! IsActive ( ) )
        {
            return ;
        }

        LayoutRebuilder . MarkLayoutForRebuild ( DrivenTransform ) ;
    }

#if UNITY_EDITOR

    protected override void OnValidate ( )
    {
        SetDirty ( ) ;
    }

#endif

    protected override void OnEnable ( )
    {
        SetDirty ( ) ;

        DrivenTransformTracker . Add ( this , DrivenTransform , DrivenTransformProperties . SizeDeltaX ) ;
        DrivenTransformTracker . Add ( this , DrivenTransform , DrivenTransformProperties . SizeDeltaY ) ;
    }

    protected override void OnDisable ( )
    {
        DrivenTransformTracker . Clear ( ) ;
    }

    const int AxisX = 0 ;
    const int AxisY = 1 ;
  
    float MaxSpan ( int Axis )
    {
        float MaxSpan = 0 ;
      
        foreach ( Transform Child in transform )
        {
            RectTransform ChildTransform = Child . GetComponent < RectTransform > ( ) ;

            MaxSpan = Mathf . Max ( MaxSpan , LayoutUtility . GetPreferredSize ( ChildTransform , Axis ) ) ;
        }

        return MaxSpan ;
    }

    public void SetLayoutHorizontal ( )
    {
        DrivenTransform . sizeDelta = new Vector2 ( MaxSpan ( AxisX ) + HorizontalPadding , DrivenTransform . sizeDelta . y ) ;
    }

    public void SetLayoutVertical ( )
    {
        DrivenTransform . sizeDelta = new Vector2 ( DrivenTransform . sizeDelta . x , MaxSpan ( AxisY ) + VerticalPadding ) ;
    }
}

Oh I thought you already had a HorizontalLayoutGroup on the GameObject. You should add that to the same GameObject as the ContentSizeFitter is on.

The HorizontalLayoutGroup will be sized based on its children (except those marked to Ignore Layout, which you should do for the child Image).

Also, please see the page “Making UI elements fit the size of their content” in the docs bundled with beta 18 for some more info.

Well shoot, that worked. Now my script is useless. :frowning: But at least I learned more about how the UI system works internally, I suppose.

1 Like