This requirement can be achieved with a custom Layout implementation:
struct HStackEqualSquares: Layout {
var spacing: CGFloat = 10
private func preferredSize(subviews: Subviews, proposedWidth: CGFloat?) -> CGSize {
let subviewDimensions = subviews.map { $0.dimensions(in: .unspecified) }
let maxWidth: CGFloat = subviewDimensions.reduce(.zero) { currentMax, subviewSize in
max(currentMax, subviewSize.width)
}
let nSubviews = CGFloat(subviews.count)
let totalWidth = (nSubviews * maxWidth) + ((nSubviews - 1) * spacing)
return CGSize(width: totalWidth, height: maxWidth)
}
func sizeThatFits(
proposal: ProposedViewSize,
subviews: Subviews,
cache: inout Void
) -> CGSize {
return preferredSize(subviews: subviews, proposedWidth: proposal.width)
}
func placeSubviews(
in bounds: CGRect,
proposal: ProposedViewSize,
subviews: Subviews,
cache: inout Void
) {
let height = preferredSize(subviews: subviews, proposedWidth: bounds.width).height
var point = bounds.origin
for subview in subviews {
let placementProposal = ProposedViewSize(width: height, height: height)
subview.place(at: point, proposal: placementProposal)
point = CGPoint(x: point.x + height + spacing, y: point.y)
}
}
}
In order that the items actually expand to fill the sizes they are given, .frame(maxWidth: .infinity, maxHeight: .infinity) needs to be set on the text items. Here is how it comes together:
struct ContentView: View {
let sets = [1,3,6,8,12,16,23,43,56,76,100,101,125]
var body: some View {
VStack {
ScrollView(.horizontal, showsIndicators: false) {
HStackEqualSquares {
ForEach(sets, id: \.self) { set in
Text("\(set)")
.padding(8)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.blue)
}
}
}
Color.green
.overlay {
Text("56")
.font(.largeTitle.weight(.bold))
}
}
}
}

EDIT Following up on your comment in which you asked about a solution for older iOS versions, it would be possible to get near to the same solution by going back to my original solution (using GeometryReader) and using an HStack instead of the Layout implementation. However, you would have to guess the height that is added to the row. This height would need to be applied to the HStack as bottom padding. It means the solution doesn't quite fulfil the requirement of no hard-coded values, but maybe it is adequate for the purpose of supporting older iOS versions. The size of the padding could at least be defined as a ScaledMetric so that it adapts to changes to the text size:
@ScaledMetric(relativeTo: .body) private var bottomPadding: CGFloat = 10
Then it is used like this:
private var simpleFootprint: some View {
Text("888")
.hidden()
}
private var compositeFootprint: some View {
ZStack {
ForEach(sets, id: \.self) { set in
Text("\(set)")
}
}
.hidden()
}
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 10) { // instead of HStackEqualSquares
ForEach(sets, id: \.self) { set in
compositeFootprint // or simpleFootprint, if sufficient
.padding(8)
.overlay {
GeometryReader { proxy in
let width = proxy.size.width
Text("\(set)")
.frame(width: width, height: width)
.background(.blue)
}
}
}
}
.padding(.bottom, bottomPadding)
}