Equal width buttons in VStack

I can't wrap my head around how to make two buttons the same width, without resorting to ugly hacks like GeometryReader. What is otherwise easy to implement using UIStackView or AutoLayout, becomes a nightmare in SwiftUI.

I tried the following, plus some other variations, but nothing works. What is the "official way" to make two vertically aligned buttons of the same width?

1)

VStack(alignment: .leading) {
	Button("Short", action: {})
		.frame(maxWidth: .infinity)
	Button("Long title", action: {})
		.frame(maxWidth: .infinity)
}
.frame(maxWidth: .infinity)

2)

VStack(alignment: .leading) {
	Button(action: {}) {
		Text("Short")
			.frame(maxWidth: .infinity)
	}
	Button(action: {}) {
		Text("Long title")
			.frame(maxWidth: .infinity)
	}
}
.frame(maxWidth: .infinity)

3)

VStack(alignment: .leading) {
	Button(action: {}) {
		Text("Short")
	}.frame(maxWidth: .infinity)
	Button(action: {}) {
		Text("Long title")
	}.frame(maxWidth: .infinity)
}
.frame(maxWidth: .infinity)
Answered by Frameworks Engineer in 810473022

To make a Button flexible, you can give its label an infinite width.

VStack {
    Button(action: {}) {
        Text("Short")
            .frame(maxWidth: .infinity)
    }
    Button(action: {}) {
        Text("Long title")
            .frame(maxWidth: .infinity)
    }
}

Or...

VStack {
    Button(action: {}) {
        Spacer()
        Text("Short")
        Spacer()
    }
    Button(action: {}) {
        Spacer()
        Text("Long title")
        Spacer()
    }
}

To get an appearance similar to @darkpaw's example, you can apply the .buttonStyle(.borderedProminent) modifier to the VStack or each individual Button, and use the tint modifier to customize the colors.

SwiftUI draws the button as wide as it needs to be for the title. It's up to you to tell it how wide the button should be. I do this by creating and applying a new ButtonStyle, like this:

import SwiftUI

struct ContentView: View {
    var body: some View {
		VStack(alignment: .center) {
			Button("Short", action: {})
				.buttonStyle(MyButtonStyle(bgColor: .gray, fgColor: .white))
			Button("Long title", action: {})
				.buttonStyle(MyButtonStyle(bgColor: .yellow, fgColor: .black))
			Button("A longer button title", action: {})
				.buttonStyle(MyButtonStyle(bgColor: .green, fgColor: .white))
			Button("A much, much, much longer button title", action: {})
				.buttonStyle(MyButtonStyle(bgColor: .blue, fgColor: .white))
		}
    }
}


struct MyButtonStyle: ButtonStyle {
	var bgColor: Color?
	var fgColor: Color?

	func makeBody(configuration: Configuration) -> some View {
		configuration.label
			.font(.headline)
			.bold()
			.allowsTightening(true)
			.lineLimit(2)
			.frame(width: 200, height: 60)
			.background(bgColor ?? .gray)
			.foregroundStyle(fgColor ?? .white)
			.clipShape(RoundedRectangle(cornerRadius: 16))
	}
}

#Preview {
    ContentView()
}

That example gives you this:

Note that you can change the number of lines in the button by changing .lineLimit().

To make a Button flexible, you can give its label an infinite width.

VStack {
    Button(action: {}) {
        Text("Short")
            .frame(maxWidth: .infinity)
    }
    Button(action: {}) {
        Text("Long title")
            .frame(maxWidth: .infinity)
    }
}

Or...

VStack {
    Button(action: {}) {
        Spacer()
        Text("Short")
        Spacer()
    }
    Button(action: {}) {
        Spacer()
        Text("Long title")
        Spacer()
    }
}

To get an appearance similar to @darkpaw's example, you can apply the .buttonStyle(.borderedProminent) modifier to the VStack or each individual Button, and use the tint modifier to customize the colors.

It seems that I'm missing something cause it doesn't work for me. Whenever I use this code:

VStack {
    Button(action: {}) {
        Text("Short")
            .frame(maxWidth: .infinity)
    }
    Button(action: {}) {
        Text("Long title")
            .frame(maxWidth: .infinity)
    }
}

I get this:

Then I added the "buttonStyle" over the whole button thinking that it'll extend the style to the whole button, but I only get a blue tint over the default length buttons, like this:

VStack {
    Button(action: {}) {
        Text("Short")
            .frame(maxWidth: .infinity)
    }
    .buttonStyle(.borderedProminent)

    Button(action: {}) {
        Text("Long title")
            .frame(maxWidth: .infinity)
    }
    .buttonStyle(.borderedProminent)
}

Equal width buttons in VStack
 
 
Q