| | |
| | | // |
| | | // Created by 倪路朋 on 6/26/25. |
| | | // |
| | | import SwiftUI |
| | | |
| | | struct FlowLayout: Layout { |
| | | var spacing: CGFloat = 8 |
| | | var lineSpacing: CGFloat = 8 |
| | | |
| | | func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize { |
| | | var width: CGFloat = 0 |
| | | var height: CGFloat = 0 |
| | | var currentLineWidth: CGFloat = 0 |
| | | var currentLineHeight: CGFloat = 0 |
| | | let maxWidth = proposal.width ?? .infinity |
| | | |
| | | for view in subviews { |
| | | let size = view.sizeThatFits(.unspecified) |
| | | if currentLineWidth + size.width > maxWidth { |
| | | width = max(width, currentLineWidth) |
| | | height += currentLineHeight + lineSpacing |
| | | currentLineWidth = size.width |
| | | currentLineHeight = size.height |
| | | } else { |
| | | currentLineWidth += size.width + spacing |
| | | currentLineHeight = max(currentLineHeight, size.height) |
| | | } |
| | | } |
| | | |
| | | width = max(width, currentLineWidth) |
| | | height += currentLineHeight |
| | | |
| | | return CGSize(width: width, height: height) |
| | | } |
| | | |
| | | func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) { |
| | | var x: CGFloat = 0 |
| | | var y: CGFloat = 0 |
| | | var lineHeight: CGFloat = 0 |
| | | |
| | | for view in subviews { |
| | | let size = view.sizeThatFits(.unspecified) |
| | | |
| | | if x + size.width > bounds.width { |
| | | x = 0 |
| | | y += lineHeight + lineSpacing |
| | | lineHeight = 0 |
| | | } |
| | | |
| | | view.place( |
| | | at: CGPoint(x: bounds.minX + x, y: bounds.minY + y), |
| | | proposal: ProposedViewSize(width: size.width, height: size.height) |
| | | ) |
| | | |
| | | x += size.width + spacing |
| | | lineHeight = max(lineHeight, size.height) |
| | | } |
| | | } |
| | | } |
| | | // MARK: - 使用示例 |
| | | |
| | | struct FlowLayoutExample: View { |
| | | let tags = [ |
| | | "Swift" |
| | | ] |
| | | |
| | | @State private var newTag = "" |
| | | @State private var customTags = ["自定义标签1", "自定义标签2"] |
| | | |
| | | var body: some View { |
| | | VStack{ |
| | | VStack(spacing: 20) { |
| | | FlowLayout(){ |
| | | |
| | | ForEach(tags, id: \.self) { item in |
| | | Text(item) |
| | | .padding(.horizontal, 12) |
| | | .padding(.vertical, 6) |
| | | .background(Color.blue.opacity(0.2)) |
| | | .cornerRadius(8) |
| | | } |
| | | }.frame(alignment:.leading) |
| | | .background(Color.red) |
| | | } |
| | | .frame(maxWidth: .infinity,alignment:.leading) |
| | | } |
| | | .background(Color.black) |
| | | } |
| | | |
| | | 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() |
| | | } |
| | | } |