SwiftUI中UIStackView.Distribution.fillEqually的等效方式

huangapple go评论74阅读模式
英文:

SwiftUI equivalent of UIStackView.Distribution.fillEqually

问题

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)
英文:

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:
SwiftUI中UIStackView.Distribution.fillEqually的等效方式

And this is what I currently have:
SwiftUI中UIStackView.Distribution.fillEqually的等效方式

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.

答案1

得分: 1

可以通过获取列表中视图的最大高度来实现这一点。

只需将你的AccountSelectionView替换为以下代码。

    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()
      }
    }

这是用于保存最大高度的首选键。

    struct HeightPreferenceKey: PreferenceKey {
        static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
            nextValue()
        }
        
        typealias Value = CGSize
        static var defaultValue: Value = .zero
    }
英文:

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
}

huangapple
  • 本文由 发表于 2023年3月9日 13:42:02
  • 转载请务必保留本文链接:https://go.coder-hub.com/75680810.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定