1

再会。我正在尝试根据元素的大小发布元素的动态放置。在空项目中工作,但有时不显示整个文本。在一个工作项目中,最多显示 2-3 个元素。也许我走错了路。请告诉我看哪个方向。我想使用 UICollectionView 和 UIViewRepresentable 但无法弄清楚到底如何。网上有例子,但有一个通用的网格,列的宽度相同。我只需要从左到右逐行铺设不同宽度的元素。

struct CustomFlexBoxView<Content> : View where Content: View {
    let alignment: Alignment
    let spacing: CGFloat
    let content: [Content]
    @State private var sizeBody: CGSize? = nil
    @State private var sizeItems: [Int:CGSize] = [:]
    init(alignment: Alignment = .center, spacing: CGFloat = 0, content: [Content]) {
        self.spacing = spacing
        self.alignment = alignment
        self.content = content
    }
    var body: some View {
        GeometryReader { (geo) in
            if let sizeBody = self.sizeBody {
                self.contentView(sizeBody: sizeBody)
            }
            else {
                self.contentFirstView
                    .onAppear {
                        self.sizeBody = geo.frame(in: .global).size
                    }
            }
        }
    }
    private var contentFirstView: some View {
        let items = self.content
        return VStack(spacing: 0) {
            ForEach(0 ..< items.count) { (index) in
                HStack(spacing: 0) {
                    items[index]
                }
                .background(
                    GeometryReader { (geo) in
                        Color.clear.onAppear {
                            self.sizeItems[index] = geo.frame(in: .global).size
                        }
                    }
                )
            }
        }
    }
    private func contentView(sizeBody: CGSize) -> some View {
        let items = self.content
        var rowWidth: CGFloat = 0
        var rowItems: [Content] = []
        var rows: [AnyView] = []
        for index in 0 ..< items.count {
            if let size = self.sizeItems[index] {
                if rowWidth + size.width + self.spacing <= sizeBody.width {
                    let addSpacing = (rowItems.isEmpty ? 0 : self.spacing)
                    rowItems.append(items[index])
                    rowWidth = rowWidth + size.width  + addSpacing
                }
                else {
                    if rowItems.isEmpty == false {
                        rows.append(
                            AnyView(
                                self.createRow(items: rowItems)
                            )
                        )
                        rowWidth = 0
                        rowItems = []
                    }
                    rowWidth = size.width
                    rowItems = [ items[index] ]
                }
            }
            else {
                if rowItems.isEmpty == false {
                    rows.append(
                        AnyView(
                            self.createRow(items: rowItems)
                        )
                    )
                    rowWidth = 0
                    rowItems = []
                }
                rows.append(AnyView(items[index]))
            }
        }
        if rowItems.isEmpty == false {
            rows.append(
                AnyView(
                    self.createRow(items: rowItems)
                )
            )
            rowWidth = 0
            rowItems = []
        }
        return AnyView (
            VStack(alignment: self.alignment.horizontal, spacing: self.spacing) {
                ForEach(0 ..< rows.count) { ind in
                    rows[ind]
                }
            }
        )
    }
    private func createRow(items: [Content]) -> some View {
        HStack(alignment: self.alignment.vertical, spacing: self.spacing) { [items] in
            ForEach(0 ..< items.count) { ind in
                items[ind]
            }
        }
    }
}

在一个空项目上一切正常,在工作项目中显示前 2-3 个元素。:

struct ContentView: View {
    @State var data: [Int] = [
        113, 2, 2342343, 234, 234234234234324, 3,
        45345435345345, 545, 34, 4, 345345345, 45345, 5, 5
    ]
    var body: some View {
        ScrollView {
            CustomFlexBoxView(
                alignment: .topLeading,
                spacing: 10,
                content: self.data.map { TestText(text: "\($0)") }
            )
        }
        .padding()
    }
}
struct TestText : View {
    let text: String
    @State private var color: Bool = false
    var body: some View {
        Text(self.text)
            .lineLimit(1)
            .fixedSize()
            .background(self.color ? Color.orange : Color.gray)
            .foregroundColor(self.color ? Color.green : Color.black)
            .onTapGesture {
                self.color.toggle()
            }
    }
}

在此处输入图像描述

但是,如果您添加更多元素,则由于某种原因,无法正确估计 CustomFlexBoxView 的总大小:

    var body: some View {
        ScrollView {
            Rectangle()
                .fill(Color.orange)
                .frame(maxWidth: .infinity, minHeight: 100)
            CustomFlexBoxView(
                alignment: .topLeading,
                spacing: 10,
                content: self.data.map { TestText(text: "\($0)") }
            )
            Rectangle()
                .fill(Color.orange)
                .frame(maxWidth: .infinity, minHeight: 100)
        }
        .padding()
    }

在此处输入图像描述

4

1 回答 1

0

我为我的项目制定了解决方案,但我不确定这是否总是有效。

struct CustomFlexBoxView<Item, Content> : View where Item: Hashable, Content: View {
    let alignment: Alignment
    let spacing: CGFloat
    let items: [Item]
    let content: (Int) -> Content
    
    @State private var sizeBody: CGSize? = nil
    @State private var widthItems: [Item: CGFloat] = [:]
    
    init(alignment: Alignment = .center, spacing: CGFloat = 0, items: [Item], @ViewBuilder content: @escaping (Int) -> Content) {
        self.spacing = spacing
        self.alignment = alignment
        self.items = items
        self.content = content
    }
    
    var body: some View {
        self.contentView
            .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: self.alignment)
            .background(
                GeometryReader { (geo) in
                    Color.clear.onAppear {
                        self.sizeBody = geo.frame(in: .global).size
                    }
                }
            )
    }
    
    private var contentView: some View {
        VStack(alignment: self.alignment.horizontal, spacing: self.spacing) {
            ForEach(self.rowsIndices, id: \.self) { (row) in
                self.createRow(indices: row)
            }
        }
    }
    
    private func createRow(indices: [Int]) -> some View {
        HStack(alignment: self.alignment.vertical, spacing: self.spacing) {
            ForEach(indices, id: \.self) { (index) in
                Group {
                    self.content(index)
                }
                .background(
                    GeometryReader { (geo) in
                        Color.clear.onAppear {
                            self.widthItems[self.items[index]] = geo.frame(in: .global).size.width
                        }
                    }
                )
            }
        }
    }
    
    private var rowsIndices: [[Int]] {
        guard let widthBody = self.sizeBody?.width else {
            return self.items.indices.map { [ $0 ] }
        }
        var rowWidth: CGFloat = 0
        var rowItems: [Int] = []
        var rows: [[Int]] = []
        for index in 0 ..< items.count {
            if  let widthItem = self.widthItems[self.items[index]] {
                let rowWidthNext = rowWidth + widthItem + (rowItems.isEmpty ? 0 : self.spacing)
                if rowWidthNext <= widthBody {
                    rowItems.append(index)
                    rowWidth = rowWidthNext
                }
                else {
                    if rowItems.isEmpty == false {
                        rows.append(rowItems)
                        rowWidth = 0
                        rowItems = []
                    }
                    rowWidth = widthItem
                    rowItems = [ index ]
                }
            }
            else {
                if rowItems.isEmpty == false {
                    rows.append(rowItems)
                    rowWidth = 0
                    rowItems = []
                }
                rows.append([ index ])
            }
        }
        if rowItems.isEmpty == false {
            rows.append(rowItems)
            rowWidth = 0
            rowItems = []
        }
        return rows
    }
}
于 2021-03-19T09:48:44.123 回答