SwiftUI Layout协议流式布局异常问题求助
SwiftUI 自定义流式布局(FlowLayout)修复方案
问题根源
- 子视图尺寸计算时使用
.unspecified,导致长文本、宽按钮无法自动换行,直接溢出父视图 - 未处理单个子视图宽度超过可用空间的场景,宽元素直接越界
- 换行判断依赖
proposal.width,而非父视图实际可用的bounds.width,造成空间利用不充分
修复后的完整代码
import SwiftUI struct MessageAudioRecorder: View { var body: some View { FlowLayout { Text("Hello Hello Hello Hello Hello Hello Hello") Button { } label: { Text("111111111111111111").foregroundStyle(.blue) } Text("Hello Hello Hello Hello Hello Hello Hello Hello Hello Hello Hello Hello") Text("111111111111111111").foregroundStyle(.orange) } .padding() // 可选:给布局加内边距,避免贴边 } } #Preview { MessageAudioRecorder() } struct FlowLayout: Layout { func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) { // 计算每个子视图在当前可用宽度下的实际尺寸(限制最大宽度为bounds.width) let availableWidth = bounds.width let sizes = subviews.map { subview in subview.sizeThatFits(ProposedViewSize(width: availableWidth, height: .infinity)) } var lineX = bounds.minX var lineY = bounds.minY var lineHeight: CGFloat = 0 for index in subviews.indices { let currentSize = sizes[index] // 检查当前子视图加入后是否超出可用宽度,单个子视图超宽则直接换行 if lineX + currentSize.width > bounds.maxX, lineX != bounds.minX { lineY += lineHeight lineHeight = 0 lineX = bounds.minX } // 放置子视图 subviews[index].place( at: CGPoint(x: lineX + currentSize.width / 2, y: lineY + currentSize.height / 2), anchor: .center, proposal: ProposedViewSize(currentSize) ) lineHeight = max(lineHeight, currentSize.height) lineX += currentSize.width } } func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize { let availableWidth = proposal.width ?? .infinity // 计算每个子视图在限制宽度下的尺寸 let sizes = subviews.map { subview in subview.sizeThatFits(ProposedViewSize(width: availableWidth, height: .infinity)) } var totalHeight: CGFloat = 0 var lineWidth: CGFloat = 0 var lineHeight: CGFloat = 0 for size in sizes { // 处理单个子视图超宽的情况 let adjustedWidth = min(size.width, availableWidth) if lineWidth + adjustedWidth > availableWidth { totalHeight += lineHeight lineWidth = adjustedWidth lineHeight = size.height } else { lineWidth += adjustedWidth lineHeight = max(lineHeight, size.height) } } totalHeight += lineHeight // 返回的宽度不超过父视图提议的宽度,避免溢出 return CGSize(width: min(lineWidth, availableWidth), height: totalHeight) } }
关键修改说明
- 子视图尺寸计算:给每个子视图传递带最大宽度的
ProposedViewSize,强制长文本/宽按钮自动换行适应可用空间 - 超宽元素处理:单个子视图宽度超过可用空间时,直接调整为可用宽度,避免溢出
- 换行判断:使用
bounds.maxX(父视图实际右边界)判断是否换行,确保布局严格在可用空间内 - 尺寸适配:
sizeThatFits返回的宽度限制在父视图提议的宽度内,避免布局整体超出父视图范围
内容的提问来源于stack exchange,提问作者Ahmed Zaidan
相关产品推荐
相关产品推荐

