SwiftUI中HStack空间不足时,如何截断并隐藏末尾视图?
SwiftUI实现HStack空间不足时的截断隐藏布局
需求是:当HStack空间不足时,保留所有前置视图的理想宽度,将第一个放不下的视图截断,并隐藏后续所有视图。默认HStack会自动压缩所有子视图,无法满足该需求,我们可以通过自定义Layout来实现。
解决方案代码
import SwiftUI // 自定义布局,实现保留前置视图理想宽度、截断首个溢出视图、隐藏剩余视图的逻辑 struct TruncatingRemainingLayout: Layout { func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize { let availableWidth = proposal.width ?? .infinity var totalWidth: CGFloat = 0 var truncateIndex: Int? // 遍历子视图,计算累加宽度,找到首个会导致溢出的视图索引 for (index, subview) in subviews.enumerated() { let subviewSize = subview.sizeThatFits(.unspecified) let newTotal = totalWidth + subviewSize.width if newTotal > availableWidth { truncateIndex = index break } totalWidth = newTotal } // 如果没有溢出,返回所有视图的总宽度;否则返回可用宽度 return CGSize(width: truncateIndex != nil ? availableWidth : totalWidth, height: subviews.first?.sizeThatFits(.unspecified).height ?? 0) } func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) { let availableWidth = bounds.width var xOffset: CGFloat = bounds.minX var truncateIndex: Int? // 先确定需要截断的视图位置 for (index, subview) in subviews.enumerated() { let subviewSize = subview.sizeThatFits(.unspecified) let newX = xOffset + subviewSize.width if newX > bounds.maxX { truncateIndex = index break } xOffset = newX } xOffset = bounds.minX // 放置子视图 for (index, subview) in subviews.enumerated() { if let truncateIdx = truncateIndex { if index < truncateIdx { // 放置前面的视图,使用理想宽度 let size = subview.sizeThatFits(.unspecified) subview.place(at: CGPoint(x: xOffset, y: bounds.midY - size.height/2), proposal: ProposedViewSize(size)) xOffset += size.width } else if index == truncateIdx { // 截断当前视图,只显示剩余空间的宽度 let remainingWidth = bounds.maxX - xOffset let idealSize = subview.sizeThatFits(.unspecified) let truncatedSize = CGSize(width: remainingWidth, height: idealSize.height) subview.place(at: CGPoint(x: xOffset, y: bounds.midY - idealSize.height/2), proposal: ProposedViewSize(truncatedSize)) // 放置后直接退出,不处理后续视图 break } else { // 隐藏后续视图,不放置 continue } } else { // 所有视图都能放下,正常放置 let size = subview.sizeThatFits(.unspecified) subview.place(at: CGPoint(x: xOffset, y: bounds.midY - size.height/2), proposal: ProposedViewSize(size)) xOffset += size.width } } } } // 使用自定义布局的示例视图 struct ExampleView: View { var body: some View { TruncatingRemainingLayout { Text("Lorem") Text("ipsum") Text("dolor") Text("sit") Text("amet") Text("consectetur") .lineLimit(1) // 确保文本只显示一行,截断时自动显示省略号 Text("adipiscing") } } } struct ExampleView_Previews: PreviewProvider { static var previews: some View { ExampleView() .previewLayout(.fixed(width: 300, height: 100)) } }
代码说明
- 自定义Layout:
TruncatingRemainingLayout实现了SwiftUI的Layout协议,通过sizeThatFits计算布局所需尺寸,placeSubviews控制每个子视图的位置和显示逻辑。 - 溢出判断:遍历子视图累加理想宽度,找到第一个会导致超出可用宽度的视图索引。
- 视图放置逻辑:
- 溢出位置之前的视图,按理想宽度正常放置;
- 首个溢出的视图,仅分配剩余可用宽度,配合
lineLimit(1)让文本自动显示省略号完成截断; - 溢出位置之后的视图直接跳过放置,实现隐藏效果。
内容的提问来源于stack exchange,提问作者David
相关产品推荐
相关产品推荐

