I'm trying to make a simple VStack in SwiftUI where each view in the stack has the same height. The heights should be equal to the least amount of space required for the biggest view in the stack.
My minimum deployment target is iOS 13.
Here is an example of what I want:
And this is what I currently have:
In UIKit this is done easily by making the vertical UIStackView with .fillEqually distribution and then setting the contentCompressionResistancePriority to .required for the vertical component while keeping contentHuggingPriority to usually .defaultLow.
However in SwiftUI I'm not sure how to achieve this in a dynamic way. I don't want to set hardcoded frame heights.
I've tried setting .frame(maxHeight: .infinity)
alongside .fixedSize(horizontal: false, vertical: true)
but it doesn't seem to work in this scenario. I can do HStack with equal heights using this method and VStack with equal widths. But it doesn't seem to work for VStack with equal height and presumably also HStack equal width.
This is my code so far:
struct AccountSelectionView: View {
@ObservedObject private(set) var viewModel: AccountSelectionViewModel
var body: some View {
VStack(spacing: 12) {
ForEach(viewModel.model.accounts, id: \.title) { account in
AccountView(account: account)
.padding(16)
.frame(maxHeight: .infinity)
.overlay(RoundedRectangle(cornerSize: .init(width: 4, height: 4)).stroke(Color.outlineGrey, lineWidth: 1))
}
}
.fixedSize(horizontal: false, vertical: true)
Spacer()
}
}
struct AccountView: View {
let account: Account
var body: some View {
HStack {
Image(uiImage: account.image)
.padding(.trailing, 20)
VStack(alignment: .leading) {
Text(account.title)
.font(.appFont("Heavy", size: 16))
Text(account.message)
.font(.appFont("Roman", size: 14))
}
Spacer()
}
}
}
I've also tried adding .frame(maxHeight: .infinity)
and spacers to the AccountView but it didn't make a difference.
You can achieve this by deriving the maximum height of your view inside the list.
for that just replace your AccountSelectionView with the below code.
struct AccountSelectionView: View {
@ObservedObject private(set) var viewModel: AccountSelectionViewModel
var body: some View {
VStack(spacing: 12) {
ForEach(viewModel.model.accounts, id: \.title) { account in
AccountView(account: account)
.padding(16)
.frame(minHeight: rowHeight)
.overlay(RoundedRectangle(cornerSize: .init(width: 4, height: 4)).stroke(Color.outlineGrey, lineWidth: 1))
.background(
GeometryReader{ (proxy) in
Color.clear.preference(key: HeightPreferenceKey.self, value: proxy.size)
})
.onPreferenceChange(HeightPreferenceKey.self) { (preferences) in
let currentSize: CGSize = preferences
if (currentSize.height > self.rowHeight) {
self.rowHeight = currentSize.height
}
}
}
}
Spacer()
}
}
and here is your preferred key for saving maximum height.
struct HeightPreferenceKey: PreferenceKey {
static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
nextValue()
}
typealias Value = CGSize
static var defaultValue: Value = .zero
}