如何使 SwiftUI Grid 根据宽度均匀布局?

How to make SwiftUI Grid lay out evenly based on width?

我正在尝试使用 SwiftUI Lazy Grid 来布置具有不同长度字符串的视图。我怎样才能构建我的代码,例如如果 3 个视图不适合,它只会制作 2 列并将第 3 个视图推到下一行,这样它们就不会重叠?

struct ContentView: View {
    var data = [
    "Beatles",
    "Pearl Jam",
    "REM",
    "Guns n Roses",
    "Red Hot Chili Peppers",
    "No Doubt",
    "Nirvana",
    "Tom Petty and the Heart Breakers",
    "The Eagles"
   
    ]
    
    var columns: [GridItem] = [
        GridItem(.flexible()),
        GridItem(.flexible()),
        GridItem(.flexible())
    ]
    
    
    var body: some View {
        LazyVGrid(columns: columns, alignment: .center) {
            ForEach(data, id: \.self) { bandName in
                Text(bandName)
                    .fixedSize(horizontal: true, vertical: false)
            }
        }
        .padding()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

你可以用这个方法来实现你想要的,解决方案来源:https://www.fivestars.blog/articles/flexible-swiftui/

内容视图

struct ContentView: View {
// MARK: - PROPERTIES

var data = [
    "Beatles",
    "Pearl Jam",
    "REM",
    "Guns n Roses",
    "Red Hot Chili Peppers",
    "No Doubt",
    "Nirvana",
    "Tom Petty and the Heart Breakers",
    "The Eagles"
   
    ]

// MARK: - BODY

var body: some View {
    FlexibleView(
        availableWidth: UIScreen.main.bounds.width, data: data,
        spacing: 15,
        alignment: .leading
      ) { item in
        Text(verbatim: item)
          .padding(8)
          .background(
            RoundedRectangle(cornerRadius: 8)
              .fill(Color.gray.opacity(0.2))
           )
      }
      .padding(.horizontal, 10)
    }

}

// MARK: - PREVIEW

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

灵活视图

// MARK: - FLEXIBLE VIEW

struct FlexibleView<Data: Collection, Content: View>: View where Data.Element: Hashable {
let availableWidth: CGFloat
let data: Data
let spacing: CGFloat
let alignment: HorizontalAlignment
let content: (Data.Element) -> Content
@State var elementsSize: [Data.Element: CGSize] = [:]

var body : some View {
    VStack(alignment: alignment, spacing: spacing) {
        ForEach(computeRows(), id: \.self) { rowElements in
            HStack(spacing: spacing) {
                ForEach(rowElements, id: \.self) { element in
                content(element)
                        .fixedSize()
                        .readSize { size in
                            elementsSize[element] = size
                        }
                }
            }
        }
    }
}

func computeRows() -> [[Data.Element]] {
    var rows: [[Data.Element]] = [[]]
    var currentRow = 0
    var remainingWidth = availableWidth

    for element in data {
      let elementSize = elementsSize[element, default: CGSize(width: availableWidth, height: 1)]

      if remainingWidth - (elementSize.width + spacing) >= 0 {
        rows[currentRow].append(element)
      } else {
        currentRow = currentRow + 1
        rows.append([element])
        remainingWidth = availableWidth
      }

      remainingWidth = remainingWidth - (elementSize.width + spacing)
    }

    return rows
}
}

查看扩展

// MARK: - EXTENSION

extension View {
    func readSize(onChange: @escaping (CGSize) -> Void) -> some View {
        background(
          GeometryReader { geometryProxy in
            Color.clear
              .preference(key: SizePreferenceKey.self, value: geometryProxy.size)
          }
        )
        .onPreferenceChange(SizePreferenceKey.self, perform: onChange)
    }
}

private struct SizePreferenceKey: PreferenceKey {
    static var defaultValue: CGSize = .zero
    static func reduce(value: inout CGSize, nextValue: () -> CGSize) {}
}