From bf9e4680eb466bcb7c9cb1bef567252bb1f2bb7d Mon Sep 17 00:00:00 2001 From: Runt <qingingrunt2010@qq.com> Date: Fri, 27 Jun 2025 05:32:55 +0000 Subject: [PATCH] 1 --- LiveProject/views/FlowLayout.swift | 193 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 193 insertions(+), 0 deletions(-) diff --git a/LiveProject/views/FlowLayout.swift b/LiveProject/views/FlowLayout.swift index f3ee86a..151cd68 100644 --- a/LiveProject/views/FlowLayout.swift +++ b/LiveProject/views/FlowLayout.swift @@ -4,4 +4,197 @@ // // Created by 倪路朋 on 6/26/25. // +import SwiftUI +import SwiftUI +/// 完全自定义的自动换行布局容器 +struct FlowLayout<Data: RandomAccessCollection, Content: View>: View where Data.Element: Hashable { + // MARK: - 属性 + + /// 要显示的数据集合 + let data: Data + /// 水平间距 + let horizontalSpacing: CGFloat + /// 垂直间距 + let verticalSpacing: CGFloat + /// 对齐方式 + let alignment: HorizontalAlignment + /// 内容构建闭包 + let content: (Data.Element) -> Content + /// 总高度状态 + @State private var totalHeight: CGFloat = 0 + + // MARK: - 初始化 + + /// 初始化FlowLayout + /// - Parameters: + /// - data: 要显示的数据集合 + /// - horizontalSpacing: 水平间距,默认为8 + /// - verticalSpacing: 垂直间距,默认为8 + /// - alignment: 对齐方式,默认为.leading + /// - content: 内容构建闭包 + init( + _ data: Data, + horizontalSpacing: CGFloat = 8, + verticalSpacing: CGFloat = 8, + alignment: HorizontalAlignment = .leading, + @ViewBuilder content: @escaping (Data.Element) -> Content + ) { + self.data = data + self.horizontalSpacing = horizontalSpacing + self.verticalSpacing = verticalSpacing + self.alignment = alignment + self.content = content + } + + // MARK: - 主体视图 + + var body: some View { + GeometryReader { geometry in + self.contentView(in: geometry) + .background( + HeightReader(height: $totalHeight) + ) + } + .frame(height: totalHeight) + } + + // MARK: - 私有方法 + + /// 构建内容视图 + private func contentView(in geometry: GeometryProxy) -> some View { + var width: CGFloat = 0 + var height: CGFloat = 0 + var lastHeight: CGFloat = 0 + + return ZStack(alignment: Alignment(horizontal: alignment, vertical: .top)) { + ForEach(data.map { $0 }, id: \.self) { item in + content(item) + .padding(.trailing, horizontalSpacing) + .padding(.bottom, verticalSpacing) + .alignmentGuide(.leading) { dimensions in + // 检查是否需要换行 + if abs(width - dimensions.width) > geometry.size.width { + width = 0 + height += lastHeight + verticalSpacing + } + + let result = width + + // 更新宽度计算 + if item == data.last { + width = 0 // 重置为0,最后一项 + } else { + width -= dimensions.width + horizontalSpacing + } + + // 记录当前行高度 + lastHeight = dimensions.height + return result + } + .alignmentGuide(.top) { dimensions in + let result = height + + // 如果是最后一项,更新总高度 + if item == data.last { + height += lastHeight + verticalSpacing + } + + return result + } + } + } + } +} + +// MARK: - 高度读取器 + +/// 用于读取视图高度的辅助视图 +private struct HeightReader: View { + @Binding var height: CGFloat + + var body: some View { + GeometryReader { geometry in + Color.clear + .preference( + key: HeightPreferenceKey.self, + value: geometry.size.height + ) + } + .onPreferenceChange(HeightPreferenceKey.self) { newHeight in + DispatchQueue.main.async { + self.height = newHeight + } + } + } +} + +// MARK: - 高度偏好键 + +/// 用于传递高度值的PreferenceKey +private struct HeightPreferenceKey: PreferenceKey { + static var defaultValue: CGFloat = 0 + + static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { + value = nextValue() + } +} + +// MARK: - 使用示例 + +struct FlowLayoutExample: View { + let tags = [ + "Swift", "SwiftUI", "UIKit", "Combine", "Core Data", + "Xcode", "Interface Builder", "Core Animation", "ARKit", + "Metal", "Core ML", "Vision", "MapKit", "CloudKit" + ] + + @State private var newTag = "" + @State private var customTags = ["自定义标签1", "自定义标签2"] + + var body: some View { + VStack { + FlowLayout(customTags + tags, horizontalSpacing: 10, verticalSpacing: 10) { tag in + MButton(text:tag){ + + } + } + .padding() + .animation(.default, value: customTags) + } + } + + private func addTag() { + withAnimation { + customTags.append(newTag) + newTag = "" + } + } +} + +/// 标签视图 +struct TagView: View { + let text: String + + var body: some View { + Text(text) + .padding(.horizontal, 12) + .padding(.vertical, 8) + .background(Color.blue.opacity(0.2)) + .foregroundColor(.blue) + .cornerRadius(10) + .overlay( + RoundedRectangle(cornerRadius: 10) + .stroke(Color.blue, lineWidth: 1) + ) + .fixedSize(horizontal: true, vertical: false) // 确保文本不被截断 + } +} + +// MARK: - 预览 + +struct FlowLayout_Previews: PreviewProvider { + static var previews: some View { + FlowLayoutExample() + } +} -- Gitblit v1.9.1