You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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))
    }
}

代码说明

  1. 自定义Layout:TruncatingRemainingLayout实现了SwiftUI的Layout协议,通过sizeThatFits计算布局所需尺寸,placeSubviews控制每个子视图的位置和显示逻辑。
  2. 溢出判断:遍历子视图累加理想宽度,找到第一个会导致超出可用宽度的视图索引。
  3. 视图放置逻辑:
    • 溢出位置之前的视图,按理想宽度正常放置;
    • 首个溢出的视图,仅分配剩余可用宽度,配合lineLimit(1)让文本自动显示省略号完成截断;
    • 溢出位置之后的视图直接跳过放置,实现隐藏效果。

内容的提问来源于stack exchange,提问作者David

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.23 01:45:34