SwiftUI:如何让HStack沿多行包装子级(如集合视图)?

时间:2019-08-15 13:02:45

标签: swiftui hstack vstack

我正在尝试使用SwiftUI重新创建基本的集合视图行为:

我有许多视图(例如照片)在水平方向彼此相邻显示。如果空间不足以在同一行上显示所有照片,则其余照片应换行到下一行。

这是一个例子:

landscape version portrait

似乎可以使用一个VStack和多个HStack元素,每个元素包含一行图片。

我尝试使用GeometryReader并遍历照片视图以动态创建这样的布局,但是它无法编译(包含声明的闭包不能与函数构建器'ViewBuilder'一起使用)。是否可以动态创建视图并返回视图?

说明:

盒子/照片的宽度可以不同(不同于经典的“网格”)。棘手的是,我必须知道当前框的宽度,以便确定它是否适合当前行,或者是否必须开始新行。

2 个答案:

答案 0 :(得分:3)

我通过使用.position修饰符使用GeometryReader和ZStack管理了某些内容。我正在使用一种hack方法通过UIFont获取字符串宽度,但是当您处理图像时,宽度应该更易于访问。

下面的视图具有用于垂直和水平对齐的状态变量,使您可以从ZStack的任何角落开始。可能会增加不必要的复杂性,但是您应该能够使其适应您的需求。

SLURM_JOBID

答案 1 :(得分:1)

这是我使用PreferenceKeys解决此问题的方式。

public struct MultilineHStack: View {
    struct SizePreferenceKey: PreferenceKey {
        typealias Value = [CGSize]
        static var defaultValue: Value = []
        static func reduce(value: inout Value, nextValue: () -> Value) {
            value.append(contentsOf: nextValue())
        }
    }

    private let items: [AnyView]
    @State private var sizes: [CGSize] = []

    public init<Data: RandomAccessCollection,  Content: View>(_ data: Data, @ViewBuilder content: (Data.Element) -> Content) {
          self.items = data.map { AnyView(content($0)) }
    }

    public var body: some View {
        GeometryReader {geometry in
            ZStack(alignment: .topLeading) {
                ForEach(0..<self.items.count) { index in
                    self.items[index].background(self.backgroundView()).offset(self.getOffset(at: index, geometry: geometry))
                }
            }
        }.onPreferenceChange(SizePreferenceKey.self) {
                self.sizes = $0
        }
    }

    private func getOffset(at index: Int, geometry: GeometryProxy) -> CGSize {
        guard index < sizes.endIndex else {return .zero}
        let frame = sizes[index]
        var (x,y,maxHeight) = sizes[..<index].reduce((CGFloat.zero,CGFloat.zero,CGFloat.zero)) {
            var (x,y,maxHeight) = $0
            x += $1.width
            if x > geometry.size.width {
                x = $1.width
                y += maxHeight
                maxHeight = 0
            }
            maxHeight = max(maxHeight, $1.height)
            return (x,y,maxHeight)
        }
        if x + frame.width > geometry.size.width {
            x = 0
            y += maxHeight
        }
        return .init(width: x, height: y)
    }

    private func backgroundView() -> some View {
        GeometryReader { geometry in
            Rectangle()
                .fill(Color.clear)
                .preference(
                    key: SizePreferenceKey.self,
                    value: [geometry.frame(in: CoordinateSpace.global).size]
                )
        }
    }
}

您可以像这样使用它:

struct ContentView: View {
    let texts = ["a","lot","of","texts"]
    var body: some View {
        MultilineHStack(self.texts) {
            Text($0)
        }
    }
}

它不仅适用于Text,而且适用于任何视图。