SwiftUI:网格项内容超出列宽时如何移至下一行避免文本换行?
解决SwiftUI网格布局中长内容项单独换行的问题
核心思路
你需要的是流式布局(而非固定列数的网格):短内容项自动填充一行(默认三列),长内容项单独占一行,同时保证文本不换行。LazyVGrid的adaptive模式无法实现单个项的跨行调整,因此需要自定义流式布局来满足需求。
步骤1:实现自定义流式布局FlowLayout
添加以下自定义Layout结构体到你的代码中:
struct FlowLayout: Layout { var spacing: CGFloat = 8 // 可根据需求调整间距 func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize { guard !subviews.isEmpty else { return .zero } let maxWidth = proposal.width ?? .infinity var currentWidth: CGFloat = 0 var currentHeight: CGFloat = 0 var lineHeight: CGFloat = 0 for subview in subviews { let itemSize = subview.sizeThatFits(.unspecified) // 当前行放不下时,换行 if currentWidth + itemSize.width + spacing > maxWidth, currentWidth > 0 { currentHeight += lineHeight + spacing currentWidth = itemSize.width lineHeight = itemSize.height } else { currentWidth += itemSize.width + (currentWidth == 0 ? 0 : spacing) lineHeight = max(lineHeight, itemSize.height) } } currentHeight += lineHeight return CGSize(width: maxWidth, height: currentHeight) } func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) { guard !subviews.isEmpty else { return } let maxWidth = bounds.width var x = bounds.minX var y = bounds.minY var lineHeight: CGFloat = 0 for subview in subviews { let itemSize = subview.sizeThatFits(.unspecified) // 当前行放不下时,换行 if x + itemSize.width > maxWidth, x > bounds.minX { y += lineHeight + spacing x = bounds.minX lineHeight = itemSize.height } else { lineHeight = max(lineHeight, itemSize.height) } // 放置子视图 subview.place(at: CGPoint(x: x, y: y), proposal: ProposedViewSize(itemSize)) x += itemSize.width + spacing } } }
步骤2:修改TagView确保文本不换行
在你的TagView中,给文本添加以下修饰符,保证文本不换行且保持固有宽度:
Text(tag.name) .lineLimit(1) // 限制单行 .fixedSize() // 保持文本固有宽度,不压缩不换行 // 其他样式修饰符...
步骤3:替换LazyVGrid为FlowLayout
把原来的LazyVGrid部分替换为自定义的FlowLayout:
var filterBar: some View { VStack { filterBarTitle if filtersOpened { // 移除原来的columns定义,替换为FlowLayout FlowLayout(spacing: 0) { // 这里的spacing和你原来的一致 ForEach(allRecipeTags) { recipeTag in TagView(tag: recipeTag, selected: selectedTags.contains(recipeTag), canToggle: true, onTap: { if selectedTags.contains(recipeTag) { print("removing \(recipeTag)") selectedTags.removeAll(where: { $0 == recipeTag }) } else { print("adding \(recipeTag)") selectedTags.append(recipeTag) } }) .border(.red) } } .border(.blue) } } .padding([.top, .bottom]) .border(width: 1, edges: [.bottom], color: Color("24292F").opacity(0.3)) .padding(.horizontal) .padding([.bottom], filtersOpened ? 8 : 0) }
效果说明
- 当所有TagView的宽度都不超过容器宽度的1/3时,会自动排列成三列;
- 遇到长文本的TagView时,该View会自动换行单独占一行;
- 所有文本都保持单行显示,不会被压缩或换行。
内容的提问来源于stack exchange,提问作者MarcelQT
相关产品推荐
相关产品推荐

