Dynamically sized scrolling frame with SCALE

I absolutely need to use scale for the contents of my ScrollingFrame and I need the CanvasSize to automatically adjust depending on how many items there are. I am using a UIGridLayout to scale my items.

Everything I’ve seen that does this uses offset to accomplish this. I need to use scale. I have no clue where to begin!

If it helps, the size of my items are {0.21, 0},{0.1, 0}. Does anyone have a clue what to do?

18 Likes

You can use the AbsoluteContentSize property of the GridLayout object, and also apply some extra padding.

3 Likes

I’ve got your problem too. I was gonna complain about it yesterday with a feature request or something, but I decided against it for whatever reason.

At this stage I’m using scale for the size of the items in the ScrollingFrame, and because of how my game works with selling items, only hoarders will probably fill it up to a size where it needs to scroll. However, I haven’t tested mobile users yet.

LUCKILY we do have one solution. It kinda sucks but it’s also a fine solution. What is it, you ask? Well we just manually resize it using offset every time the AbsoluteSize of the ScrollingFrame changes. It’s easy enough:

-- Desired values in scale
local PADDING = Vector2.new(0.02, 0.02)
local SIZE = Vector2.new(0.2, 0.2)

ScrollingFrame:GetPropertyChangedSignal("AbsoluteSize"):Connect(function()
    local AbsoluteSize = ScrollingFrame.AbsoluteSize

    -- Convert desired values to offset
    local NewPadding = PADDING * AbsoluteSize
    NewPadding = UDim2.new(0, NewPadding .X, 0, NewPadding .Y)
    local NewSize = SIZE * AbsoluteSize
    NewSize = UDim2.new(0, NewSize.X, 0, NewSize.Y)

    UIGridLayout.CellPadding = NewPadding
    UIGridLayout.CellSize = NewSize
end)

I’ll probably implement this into my own game when I feel like it. Now that I think about it, it’s surprisingly easy, so I have no reason to procrastinate hah.

The problem here is that AbsoluteContentSize is what the Scale property of objects inside the ScrollingFrame use. As AbsoluteContentSize increases, so does the size of the grid items and the padding (if they were sized with Scale). To fix this, the AbsoluteSize property is what should be used in this case as it is completely unrelated to the scrolling canvas (and therefore relatively static).

45 Likes

Hmm… It saddens me that nobody seems to have found a solution for this that actually uses scale. I really wish they had support for this built-in.

I will mess around tomorrow and see if I can figure something out. I’ll try your solution too. Thanks

2 Likes

So I finally got the chance to try your solution and it didn’t work. Still not showing all my items :frowning:

EDIT: I actually got it working. Thanks for doing this!

3 Likes

Put a UIAspectRatioConstraint parented to the UIGridLayout.

4 Likes

Well it took 4 months but this sounds like a good idea. I’ll keep that in mind in the future.

3 Likes

Just tested this. It doesn’t work. Oof.

2 Likes

I normally set the CanvasSize to UDim2.new(0, 0, 0, 0) then I design a template of the contents in the ScrollingFrame and set up the UIGridLayout then once I am happy with it I insert the UIAspectRatioConstraint parented to the UIGridLayout. Then you change the CanvasSize based on the AbsoluteContentSize.Y of the UIGridLayout.

--CanvasSize
UIGridLayout.Changed:Connect(function()
	ScrollingFrame.CanvasSize = UDim2.new(0, 0, 0, UIGridLayout.AbsoluteContentSize.Y)
end)

image

13 Likes

I hate to bump this thread but I ran into this problem again. I realized your solution doesn’t actually adjust the CanvasSize of the ScrollingFrame. How can I adjust that so that the scrollbar works properly?

2 Likes

ScrollingFrame.CanvasSize = UIGridLayout.AbsoluteContentsSize at the end of that callback function should do it.

3 Likes

I appreciate you looking at this again. That didn’t work though. The scrollbar isn’t the proper length. I had to modify your code as well to get rid of an error, so maybe I messed something up? This is what it looks like right now:

ScrollingFrame:GetPropertyChangedSignal("AbsoluteSize"):Connect(function()
   local AbsoluteSize = ScrollingFrame.AbsoluteSize
   local NewPadding = PADDING * AbsoluteSize
   NewPadding = UDim2.new(0, NewPadding .X, 0, NewPadding .Y)
   local NewSize = SIZE * AbsoluteSize
   NewSize = UDim2.new(0, NewSize.X, 0, NewSize.Y)

   UIGridLayout.CellSize = NewSize
   ScrollingFrame.CanvasSize = UDim2.new(0, UIGridLayout.AbsoluteContentSize.X, 0, UIGridLayout.AbsoluteContentSize.Y)
end)
1 Like

Oh, sorry about those errors I was quite rushed when I was writing it at the time :wink:

That could should work fine AFAIK, when I have used it in the past the canvas size fit perfectly. How off is the scrollbar length? Is it just a little bit or is it massive?

Just looking at your code, I can only see one issue - the line setting the cell padding (UIGridLayout.CellPadding = NewPadding) is missing. That’s probably the cause if the scrollbar length isn’t off by much.

1 Like

That gives me huge padding:
Nerd%20hair

The scrollbar is only slightly shorter than what it needs to be though.

1 Like

The PADDING variable is supposed to be in Scale measurements – in my original post PADDING was (0.02, 0.02).

If you want PADDING to be in Offset, just remove the part where it multiplies by AbsoluteSize:

local NewPadding = PADDING * AbsoluteSize

1 Like

Now the scrollbar is way too short.

I’m about to give up.

I’ve simplified your code almost completely, does this help? Try using this exact code.

local PADDING = Vector2.new(10, 10) -- Offset
local SIZE = Vector2.new(0.2, 0.2) -- Scale

-- This only needs to be set once since the padding is not in Scale
UIGridLayout.CellPadding = UDim2.new(0, PADDING.X, 0, PADDING.Y)

ScrollingFrame:GetPropertyChangedSignal("AbsoluteSize"):Connect(function()
    local NewSize = SIZE * ScrollingFrame.AbsoluteSize
    UIGridLayout.CellSize = UDim2.new(0, NewSize.X, 0, NewSize.Y)
    ScrollingFrame.CanvasSize = UDim2.new(0, UIGridLayout.AbsoluteContentSize.X, 0, UIGridLayout.AbsoluteContentSize.Y)
end)

You can also remove the code to do with padding completely and set it all in the editor if you want to make this as simple as possible (which is a good idea if you’re having issues).

The only issue with my code I can see is that it does not account for padding when calculating the scale of elements. To do this we’d need to do something like:

-- Add one because with two elements there'd be three padding spaces, and so on
local NumColumns = (1 / SIZE.X) + 1 

-- We only take away from the X axis because the Y axis is what makes the scrolling frame expand
-- However if you want to keep aspect ratio, do NewSize.Y - UIGridLayout.CellPadding.Y.Offset
NewSize = Vector2.new(NewSize.X - (UIGridLayout.CellPadding.X.Offset * NumColumns), NewSize.Y)
5 Likes

local scrollingFrame = script.Parent
local uiGridLayout = scrollingFrame.UIGridLayout
local SIZE = Vector2.new(uiGridLayout.CellSize.X.Scale, uiGridLayout.CellSize.Y.Scale)
local PADDING = Vector2.new(uiGridLayout.CellPadding.X.Scale, uiGridLayout.CellPadding.Y.Scale)

local function ResetScroll()
	local NewSize = SIZE * scrollingFrame.AbsoluteSize
	local NewPadding = PADDING * scrollingFrame.AbsoluteSize
	uiGridLayout.CellSize = UDim2.new(0, NewSize.X, 0, NewSize.Y)
	uiGridLayout.CellPadding = UDim2.new(0, NewPadding.X, 0, NewPadding.Y)   
	scrollingFrame.CanvasSize = UDim2.new(0, uiGridLayout.AbsoluteContentSize.X, 0, uiGridLayout.AbsoluteContentSize.Y)
end

ResetScroll()
uiGridLayout:GetPropertyChangedSignal("AbsoluteContentSize"):Connect(ResetScroll)

I am sorry to revive this thread but I recently needed to use a similar thing in my game and I stumbled upon your answer. The idea to use AbsoluteSize property is apt but the property doesn’t change on dynamic addition/ removal of elements. So instead I connected to the property “AbsoluteContentSize” of the UIGridLayout and made some other minor modifications. Now you can set the Cell padding and Size [scale wise] in the editor and the script would look after the rest.

1 Like

Yep, however one issue there. Your code is cyclically calling ResetScroll – when the AbsoluteContentSize changes you call ResetScroll to modify the CellSize, which then changes the AbsoluteContentSize and then you call ResetScroll… and so on.

In your case this might not be much of an issue as its probably only looping maybe twice or thrice due to your calculations being deterministic, but it is prone to error (and is doing redundant work) which is why I think it’s good practice to avoid that.

Another thing to note is that now resizing the screen (and/or the ScrollingFrame and its ancestors) will not call ResetScroll because the AbsoluteContentSize doesn’t change because the cells have their sizes in Offset, not in Scale. So the AbsoluteSize listener is still necessary if you want to be robust and handle this.

TLDR calling it like you are now will call it at least twice for every time an item or a row (depending on the number of items in the grid) is added or removed, and will not call it at all when the screen size changes.

What I think is better is binding ResetScroll to when AbsoluteSize changes, and also calling it manually after you add/remove elements from the grid:

ScrollingFrame:GetPropertyChangedSignal("AbsoluteSize"):Connect(ResetScroll)

-- when you're doing stuff with items
for i = 1, #ItemsToGenerate do
   local Item = MakeNewItem()
   Item.Parent = ScrollingFrame
end
ResetScroll()

This does less work, as now the ResetScroll function only gets called when a) the ScrollingFrame changes size (usually due to screen size changing, which is rare), or b) you’re done modifying the items in the grid and want to tidy it up.

1 Like

I created a library to easily handle the sizing of UIGridLayouts and UIListLayouts, you could check it out here:

3 Likes