SwiftUI实现UIStackView.Distribution.fillEqually等效效果的问题
在SwiftUI中实现VStack内视图等高(基于最大视图高度)
需求:iOS 13+,创建VStack,使栈内每个视图高度相同,高度等于栈内最大视图所需的最小空间,不使用硬编码高度。
效果对比
- 期望效果:所有账户卡片高度统一,高度由内容最多的卡片决定
- 当前效果:卡片高度随各自内容多少变化,高度参差不齐
UIKit中的实现方式
在UIKit中只需创建垂直UIStackView,设置其distribution为.fillEqually,同时将垂直方向的contentCompressionResistancePriority设为.required,contentHuggingPriority保持默认的.defaultLow即可实现该效果。
SwiftUI中的尝试
尝试过将.frame(maxHeight: .infinity)与.fixedSize(horizontal: false, vertical: true)结合使用,但无效;也尝试在AccountView中添加.frame(maxHeight: .infinity)和Spacer,同样没有变化。
当前代码
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() } } }
解决方案(iOS 13兼容)
通过PreferenceKey收集所有子视图的高度,计算出最大值后统一应用到每个视图:
- 定义PreferenceKey用于传递高度值:
struct ViewHeightKey: PreferenceKey { static var defaultValue: CGFloat = 0 static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { value = max(value, nextValue()) } }
- 修改
AccountSelectionView,收集高度并应用最大高度:
struct AccountSelectionView: View { @ObservedObject private(set) var viewModel: AccountSelectionViewModel @State private var maxHeight: CGFloat = 0 var body: some View { VStack(spacing: 12) { ForEach(viewModel.model.accounts, id: \.title) { account in AccountView(account: account) .padding(16) .frame(height: maxHeight) // 应用最大高度 .overlay( GeometryReader { proxy in Color.clear .preference(key: ViewHeightKey.self, value: proxy.size.height) } ) .overlay(RoundedRectangle(cornerSize: .init(width: 4, height: 4)).stroke(Color.outlineGrey, lineWidth: 1)) } } .onPreferenceChange(ViewHeightKey.self) { newHeight in maxHeight = newHeight } Spacer() } }
- 调整
AccountView适配高度:
如果需要让AccountView内的内容垂直对齐,可在外层包裹VStack并添加Spacer控制布局:
struct AccountView: View { let account: Account var body: some View { VStack { 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() } Spacer() // 让内容顶部对齐,若要居中可添加上下Spacer } } }
原理:通过GeometryReader获取每个AccountView的高度,利用PreferenceKey将所有高度传递给父视图,父视图取最大值后再设置给每个子视图,从而实现所有视图等高。
内容的提问来源于stack exchange,提问作者Cameron Porter
相关产品推荐
相关产品推荐

