import SwiftUI
struct FlexibleView<Data: Collection, Content: View>: View where Data.Element: Hashable {
let data: Data //数据元
let spacing: CGFloat //行/列间距
let availableWidth: CGFloat //总宽度
var alignment: HorizontalAlignment = .leading
var targetline: Int = 3 //预估行数, 使高度能空白填充, 和其他数量不同的地方保持高度一致
let content: (Data.Element) -> Content
@State var elementsSize: [Data.Element: CGSize] = [:]
var body: some View {
if data.isEmpty {
EmptyView()
}else{
ZStack(alignment: .topLeading) {
VStack(alignment: alignment, spacing: spacing) {
ForEach(0..<targetline, id: \.self) { _ in
content(data.first!)//每个
.fixedSize()
.opacity(0)
}
}
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) >= -4 {
rows[currentRow].append(element)
} else {
currentRow = currentRow + 1
rows.append([element])
remainingWidth = availableWidth
}
remainingWidth = remainingWidth - (elementSize.width + spacing)
}
return rows
}
}
struct SizePreferenceKey: PreferenceKey {
static var defaultValue: CGSize = .zero
static func reduce(value: inout CGSize, nextValue: () -> CGSize) {}
}
public extension View {
func readSize(onChange: @escaping (CGSize) -> Void) -> some View {//size识别
return background(
GeometryReader { geometryProxy in
Color.clear
.preference(key: SizePreferenceKey.self, value: geometryProxy.size)
}
)
.onPreferenceChange(SizePreferenceKey.self, perform: onChange)
}
}
以上
网友评论