GridLayout + ContentSizeFitter doesn't work in 4.6.1p3

I have a horizontal ScrollRect that contains a panel with a GridLayoutGroup and ContentSize fitter with horizontal fit as Preferred Size and vertical fit as Unconstrained. I noticed that sometimes the scroll bar would let me scroll even when the objects fit on the panel just fine and sometime wouldn’t let me scroll to the end.
When I change the Vertical Fit to Preferred Size the panel seems to start matching what I’m actually seeing happening with the scroll panel, so it seems that the Content Size Fitter is using Preferred Size instead of Unconstrained fit.

Here’s a picture, as you can see the green child panels go beyond the red panel with the content size fitter:

I reported a bug with case number 657495.

Here’s two more pictures to make it clearer.

First, this is what happens with the flexible GridLayoutGroup and ContentSizeFitter with horizontal fit as preferred and vertical fit as unconstrained:

It’s not working as it’s supposed to, the panel width is calculated wrong.

Then the only change I make is change the vertical fit to preferred size and it changes to this:

Note that the scroll bar didn’t change at all but now matches the panel size. The panel’s width didn’t change, but the height did, so it seems it always calculates the “preferred size” width as if the vertical fit was “preferred size” too.

Not fixed in 4.6.1p2. I know my explanation wasn’t very clear, but the two pictures should make it very clear what happens.

Not fixed in 4.6.1p3. I created a new bug report with a new repro project, ticket number 662142. I would really appreciate some kind of reply from the Unity folks.

Not fixed in 4.6.1p4. At least the QA said they were able to reproduce it and have sent it forward.

QA replied:

Just… wow. That’s some seriously horrible design then. I’d think it’s not too much ask to have a layout with a dynamical row count based on available space and horizontal stretching.

While testing a workaround I noticed that the Flexible GridLayoutGroup + ContentSizeFitter with Unconstrained + PreferredSize fitting already works vertically, just not horizontally! I can’t believe that’s by design and not a bug.

Anyway, here’s my workaround, if someone needs it. It dynamically sets the GridLayoutGroup rows based on the RectTransform size. Add the script to a RectTransform that has a GridLayoutGroup and a ContentSizeFitter component.

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

[RequireComponent(typeof(GridLayoutGroup))]
[RequireComponent(typeof(ContentSizeFitter))]
[RequireComponent(typeof(RectTransform))]
public class DynamicGridSize : UIBehaviour
{
    private GridLayoutGroup grid;
    private ContentSizeFitter fitter;
    bool initialized = false;

    protected override void Awake()
    {
        base.Awake();
        grid = GetComponent<GridLayoutGroup>();
        fitter = GetComponent<ContentSizeFitter>();
        initialized = true;
    }

    protected override void Start()
    {
        base.Start();
        CalculateGridSize();
    }

    protected override void OnRectTransformDimensionsChange()
    {
        base.OnRectTransformDimensionsChange();
        CalculateGridSize();
    }
   
    public void CalculateGridSize()
    {
        // Seems OnRectTransformDimensionsChange gets called before Awake
        if (!initialized)
        {
            return;
        }

        RectTransform rectTrans = (RectTransform)transform;

        // Strech vertically
        if (fitter.verticalFit == ContentSizeFitter.FitMode.PreferredSize && fitter.horizontalFit == ContentSizeFitter.FitMode.Unconstrained)
        {
            int columns = (int)(rectTrans.rect.width / (grid.cellSize.x + grid.spacing.x));
            grid.constraint = GridLayoutGroup.Constraint.FixedColumnCount;
            grid.constraintCount = columns;
        }
        // Stretch horizontally
        else if (fitter.verticalFit == ContentSizeFitter.FitMode.Unconstrained && fitter.horizontalFit == ContentSizeFitter.FitMode.PreferredSize)
        {
            int rows = (int)(rectTrans.rect.height / (grid.cellSize.y + grid.spacing.y));
            grid.constraint = GridLayoutGroup.Constraint.FixedRowCount;
            grid.constraintCount = rows;
        }
        else
        {
            // Nothing to do
            return;
        }
    }
}

Yes. It’s due to how the auto layout system works. You can read about it here, particularly under the section “Layout Calculations”:

http://docs.unity3d.com/Manual/UIAutoLayout.html

To quote from the end of it:

Hence the GridLayoutGroup can’t know calculate it’s width based on its height unless you give it extra information that lets it know its height even before the width and heights have been calculated. This is what the Constarint property is for.

I’m glad you found a solution that works for you.

I’m you’re wondering why we don’t do this in the built-in components, it’s because it creates ambiguity about whether the RectTranform height of the grid is an input or an output. Say you had the ContentSizeFitter height set to “preferred height”. Then, with your script, the height would both be read as input for the layout system and then the result would also set the height, potentially overriding your manually set height value with a different one. Sometimes this can turn out to work fine; other times it can create weird positive or negative feedback loops that can manifest as weird bugs where the grid might keep on growing by itself, or maybe oscillating in size.

Alternatively, the RectTransform should look at what is setup in the ContentSizeFitter in order to determine if it should set its row count based on the height or not, that would be a hack. ContentSizeFitter might be placed on a parent component, or the user might have created a different component that is similar to ContentSizeFitter but a bit different. So we can’t have a hard-coded check for specific ContentSizeFitter settings inside the GridLayoutGroup class.

What we can do is provide the Constraint property on the Grid Layout Group.[/QUOTE]