Question or problem in the Swift programming language:
I am trying to create 2 buttons that are equal width, positioned one above the other, vertically. It should look like this:
I have placed 2 Buttons inside a VStack which automatically expands to the width of the larger button. What I am trying to do is have the width of the buttons expand to fill the width of the VStack, but this is what I get instead:
VStack(alignment: .center, spacing: 20) { NavigationLink(destination: CustomView()) { Text("Button") }.frame(height: 44) .background(Color.primary) Button(action: { self.isShowingAlert = true }) { Text("Another Button") }.frame(height: 44) .background(Color.primary) }.background(Color.secondary)
Setting the width of the VStack expands it, but the buttons do not expand to fit:
VStack(alignment: .center, spacing: 20) { ... }.frame(width: 320) .background(Color.secondary)
So my question is:
Is there a way to do this, besides manually setting the frame of every item in my layout?
I would rather not have to specify each one as it will become difficult to manage.
How to solve the problem:
Solution 1:
Setting .infinity
as the maxWidth
, the frame(minWidth: maxWidth: minHeight:)
API can be used to make a subview expand to fill:
VStack(alignment: .center, spacing: 20) { NavigationLink(destination: CustomView()) { Text("Button") }.frame(minWidth: 100, maxWidth: .infinity, minHeight: 44) .background(Color.primary) Button(action: { self.isShowingAlert = true }) { Text("Another Button") }.frame(minWidth: 100, maxWidth: .infinity, minHeight: 44) .background(Color.primary) }.frame(width: 340) .background(Color.secondary)
Solution 2:
You will have to use the frame modifier with maxWidth: .infinity
on the Text
itself inside the button, this will force the Button
to become as wide as it can:
VStack(alignment: .center, spacing: 20) { NavigationLink(destination: CustomView()) { Text("Button") .frame(maxWidth: .infinity, height: 44) } .background(Color.primary) Button(action: { self.isShowingAlert = true }) { Text("Another Button") .frame(maxWidth: .infinity, height: 44) } .background(Color.primary) }.background(Color.secondary)
This works in iOS, but not in macOS using the default button style, which uses AppKit’s NSButton
, since it refuses to get any wider (or taller). The trick in macOS is to use the .buttonStyle()
modifier on your button (or the NavigationLink
) and make your own custom button style like so:
struct MyButtonStyle: ButtonStyle { func makeBody(configuration: Configuration) -> some View { configuration.label .background(configuration.isPressed ? Color.blue : Color.gray) } }
The reason why you must apply the frame modifier to the Text
view and not the button itself, is that the button will prefer to stick to its content’s size instead of the size suggested by the view that contains the button. What this means is that if you apply the frame modifier to the button and not the Text
inside it, the button will actually remain the same size as the Text
and the view returned by .frame
is the one that will expand to fill the width of the view that contains it, so you will not be able to tap/click the button outside the bounds of the Text
view.