1

I'm building an iOS Widget (WidgetKit) with a 2×2 button grid using GeometryReader to calculate row heights. The buttons should fill their containers completely and have equal heights, like Apple's Shortcuts widget.

Expected Result: All buttons should have equal height and fill the available space evenly, like this:

Expected: All buttons with equal height (Apple Shortcuts widget style)

Actual Result: The button backgrounds don't fill their calculated container heights. The containers themselves ARE equal (verified with debug borders showing 44pt height), but the button content/background stops short:

Actual: Button backgrounds don't fill their calculated container heights

Current Implementation:

Using GeometryReader to calculate cellHeight, then applying it with .frame(height: cellHeight):

GeometryReader { geo in
    let cellHeight = (geo.size.height - totalSpacing - totalPadding) / 2
    
    VStack(spacing: 8) {
        ForEach(0..<2) { row in
            HStack(spacing: 8) {
                ForEach(0..<2) { col in
                    WidgetActionButton(...)
                        .frame(height: cellHeight)
                }
            }
        }
    }
}

Button implementation:

Button(intent: ExecuteWidgetActionIntent(...)) {
    VStack(alignment: .leading, spacing: 8) {
        Image(systemName: action.iconName)
        Spacer(minLength: 0)
        Text(action.displayName)
    }
    .padding(12)
    .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
    .background(Color.blue)
    .clipShape(RoundedRectangle(cornerRadius: 14))
}
.buttonStyle(.plain)

The debug borders prove the containers are equal height, but the colored button backgrounds don't expand to fill them. How can I make the button backgrounds fill the full calculated height in WidgetKit?

  1. Grid with .frame(maxHeight: .infinity) on buttons:

    • Applied .frame(maxHeight: .infinity) to each button in GridRow
    • Expected: Rows would distribute height equally
    • Result: Top row still taller than bottom row
  2. Grid with .gridCellUnsizedAxes(.vertical):

    • Added .gridCellUnsizedAxes(.vertical) to GridRow
    • Expected: Grid would ignore intrinsic content size and distribute evenly
    • Result: No change, rows still unequal
  3. GeometryReader with VStack/HStack and calculated heights:

    • Calculated exact cellHeight using available space
    • Applied .frame(height: cellHeight) to each button container
    • Expected: Buttons would fill the fixed height containers
    • Result: Container heights are correct (verified with debug borders at 44pt), but button backgrounds don't expand to fill them
  4. .frame(maxHeight: .infinity) inside Button label:

    • Applied maxHeight to the VStack inside Button's label closure
    • Expected: Button content would expand to fill available space
    • Result: Button label still doesn't fill the container height
  5. ZStack with background outside Button:

    • Wrapped Button in ZStack with Color background
    • Expected: Background would fill the container independently
    • Result: Background also doesn't fill the calculated height

The core issue: In WidgetKit, the button content seems to have its own intrinsic size that doesn't respond to the parent container's fixed height, even though debug borders confirm the containers are equal.

Edit:

[![My Widget now][1]][1] [1]: https://i.sstatic.net/fzg3MSR6.png

[![ When you compare my widget to Apple's Shortcuts widget in the screenshots, you can see:

0

1 Answer 1

0

It sounds from the way that you describe the issue that you are expecting the buttons to fit inside a frame with height of 44 points. However, the VStack that forms the label of the button uses spacing of 8, then you add padding of 12. So before the image and text are even shown, a height of 40 is already required. When the image and text are shown too, the label content overflows the frame.

In order to fit inside a height of 44, the spacing and padding needs to be more flexible. I would suggest the following changes:

  • Use spacing of 0 for the VStack.
  • Scale the image to fit within a minimum and maximum height.
  • Allow the text to shrink a little by applying a .minimumScaleFactor.
  • Use Color.clear with a minimum and maximum height, instead of vertical padding.
  • Apply .layoutPriority to the different elements, to determine how the allocation of space should be prioritized.
Button {} label: {
    VStack(alignment: .leading, spacing: 0) {
        Color.clear
            .frame(minHeight: 6, maxHeight: 12)
            .layoutPriority(2)
        Image(systemName: iconName)
            .resizable()
            .scaledToFit()
            .frame(minHeight: 16, maxHeight: 32)
            .layoutPriority(3)
        Spacer(minLength: 0)
            .layoutPriority(1)
        Text(displayName)
            .minimumScaleFactor(0.8)
            .layoutPriority(3)
        Color.clear
            .frame(minHeight: 6, maxHeight: 12)
            .layoutPriority(2)
    }
    .padding(.horizontal, 12)
    .background(.blue)
    .clipShape(.rect(cornerRadius: 14))
}
.buttonStyle(.plain)

Note that the frame setting maxWidth and maxHeight to inifinity is no longer needed, because the label contains greedy elements that expand to use all the available space anyway.

Here is how it looks for heights of 44 and 100 points:

Screenshot

Sign up to request clarification or add additional context in comments.

6 Comments

Thank you for your response! I think there's a misunderstanding. The button frames already have the correct height (44pt calculated via GeometryReader). The problem is that the button CONTENT (icon + text) doesn't fill the full button frame height. There's too much empty space inside the buttons. I need the content to stretch from the top edge to the bottom edge of each button, like in Apple's Shortcuts widget. How can I make the VStack with icon and text fill the maxHeight: .infinity of the button label?
Your screenshots suggest to me that the content is overflowing the height, which is the opposite of not filling the height. With your original code, I could only reproduce the problem by setting a height that was too small. The content then overflows. This happens when the frame height is 44 pt. If the frame height is 100 pt, the content fits fine. Did you actually try the code in the answer to see if it helps?
Thank you! Your solution fixed the button content overflow. The flexible spacers, resizable icons, and layoutPriority work perfectly. However, there's a second issue: The widget grid doesn't fill the available space. Too much black padding around all edges. I've edited my question with screenshots comparing my widget to Apple's Shortcuts widget. My goal is to match that layout exactly. Current values: let outerPadding: CGFloat = 8 let spacing: CGFloat = 8 Should I reduce these to 4pt or 6pt? Is there a recommended WidgetKit approach for maximizing button size? Thank you aga
So, is there still a misunderstanding, or am I on the right track? Re. calculating the height, there is actually no need to set a fixed height on the buttons. The buttons will expand to use all the space available. So if you use a container of some sort to hold the buttons, you can use spacing between them and padding around the container. The size of the container then takes care of itself.
Thank you! Your solution worked perfectly. The flexible spacers, resizable icons, and layoutPriority fixed everything. Our widget now matches Apple's Shortcuts widget layout. However, we're now stuck with getting Liquid Glass transparency for the widget background instead of solid black. containerBackground doesn't seem to apply the glass effect like Apple's native widgets. Any insights on Liquid Glass backgrounds in iOS 26 widgets would be appreciated! Thanks again for your help!
Pleased to hear you got the layout working. If the answer helped you to the solution, perhaps you could consider accepting it, by tapping the checkmark? ;) For the glass background, you would normally apply .glassEffect to the container, but I don't know if there are restrictions with widgets. If you are having difficulty getting it working, I would suggest posting a new question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.