I am trying to put multiple cells next to each other where each cell consists of an image and a text below. The cell itself should be a square and the image should be scaled to
A ZStack will help solve this by allowing us to layer views without one effecting the layout of the other.
For the text:
.frame(minWidth: 0, maxWidth: .infinity)
to expand the text horizontally to its parent's size
.frame(minHeight: 0, maxHeight: .infinity)
is useful in other situations
As for the image:
.aspectRatio(contentMode: .fill)
to make the image maintain its aspect ratio rather than squashing to the size of its frame.
.layoutPriority(-1)
to de-prioritize laying out the image to prevent it from expanding its parent (the ZStack
within the ForEach
in our case).
The value for layoutPriority
just needs to be lower than the parent views which will be set to 0 by default. We have to do this because SwiftUI will layout a child before its parent, and the parent has to deal with the child size unless we manually prioritize differently.
The .clipped() modifier uses the bounding frame to mask the view so you'll need to set it to clip any images that aren't already 1:1 aspect ratio.
var body: some View {
HStack {
ForEach(0..<3, id: \.self) { index in
ZStack {
Image(systemName: "doc.plaintext")
.resizable()
.aspectRatio(contentMode: .fill)
.layoutPriority(-1)
VStack {
Spacer()
Text("yes")
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.white)
}
}
.clipped()
.aspectRatio(1, contentMode: .fit)
.border(Color.red)
}
}
}
Edit: While geometry readers are super useful I think they should be avoided whenever possible. It's cleaner to let SwiftUI do the work. This is my initial solution with a Geometry Reader that works just as well.
HStack {
ForEach(0..<3, id: \.self) { index in
ZStack {
GeometryReader { proxy in
Image(systemName: "pencil")
.resizable()
.scaledToFill()
.frame(width: proxy.size.width)
VStack {
Spacer()
Text("yes")
.frame(width: proxy.size.width)
.background(Color.white)
}
}
}
.clipped()
.aspectRatio(1, contentMode: .fit)
.border(Color.red)
}
}