So I just need the views within an HSTack
to each take the entire width available to them so the space is evenly distributed and all have the same width.
As for height, they should also all have the same height regardless of text length.
Current code:
struct TestViewHStack: View {
let strings = ["Short", "some long text", "short"]
var body: some View {
HStack {
ForEach(strings, id: \.self) { title in
CardView(title: title)
.frame(maxWidth: .infinity)
}
}
}
}
struct CardView: View {
let title: String
var body: some View {
VStack(spacing: 8) {
Image(systemName: "star.fill")
.resizable()
.frame(width: 20, height: 20)
Text(title)
.font(.subheadline)
}
.padding(16)
.background(Color.white)
.clipShape(RoundedRectangle(cornerRadius: 16))
.shadow(color: .gray.opacity(0.2), radius: 8, x: 0, y: 2)
}
}
Output:
I thought setting .frame(maxWidth: .infinity)
would make it each take the entire width available, but it doesnt seem to do that. Also tried messing with frames and fixedSize()
modifiers but wasnt able to achieve the desired result.
Thanks!
For the width, you were doing it the right way by using maxWidth: .infinity
, except that this needs to be applied before the background and other decorations (like shadow and clip shape). So:
.frame
modifier from TestViewHStack
to CardView
CardView
to TestViewHStack
.The first way involves less changes (but see the note about reusability after the screenshot below).
For the height, I would suggest one of the following two techniques:
Text(title)
.font(.subheadline)
.lineLimit(2, reservesSpace: true)
If you just want to use the minimum space necessary, then the space needed can be established using a hidden version of the same content. The hidden version forms the footprint and its height will be the height of the tallest card. The visible content is then shown as an overlay over the footprint.
An overlay is constrained by the size of the view it is applied to, so maxHeight: .infinity
can be used to have all the cards expand to the full height of the overlay, this being the height of the tallest card. Use alignment: .top
if you want the stars to be aligned at the top:
struct TestViewHStack: View {
let strings = ["Short", "some long text", "short"]
private func cardRow(fullHeight: Bool = false) -> some View {
HStack {
ForEach(strings, id: \.self) { title in
CardView(title: title, maxHeight: fullHeight ? .infinity : nil)
}
}
}
var body: some View {
cardRow()
.hidden()
.overlay{
cardRow(fullHeight: true)
}
}
}
struct CardView: View {
let title: String
let maxHeight: CGFloat?
var body: some View {
VStack(spacing: 8) {
Image(systemName: "star.fill")
.resizable()
.frame(width: 20, height: 20)
Text(title)
.font(.subheadline)
}
.padding(16)
.frame(maxWidth: .infinity, maxHeight: maxHeight, alignment: .top)
.background(Color.white)
.clipShape(RoundedRectangle(cornerRadius: 16))
.shadow(color: .gray.opacity(0.2), radius: 8, x: 0, y: 2)
}
}
If you are concerned that moving maxWidth: .infinity
into CardView
stops it from being reusable, then you could always make maxWidth
a parameter of type CGFloat?
in just the same way as maxHeight
has been parameterized above. You could also support a default of nil
, so that it behaves in the same way as you had it before:
struct CardView: View {
let title: String
var maxWidth: CGFloat?
var maxHeight: CGFloat?
var body: some View {
VStack(spacing: 8) {
// ...
}
.padding(16)
.frame(maxWidth: maxWidth, maxHeight: maxHeight, alignment: .top)
// decoration as before
}
}
This version of CardView
behaves exactly the same as you originally had it. When used in the context of TestViewHStack
, the function cardRow
shown in this answer would need to specify the maxWidth explicitly:
ForEach(strings, id: \.self) { title in
CardView(
title: title,
maxWidth: .infinity,
maxHeight: fullHeight ? .infinity : nil
)
}