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

SwiftUI中根据空间自适应的自定义分段控件布局实现问题

SwiftUI 可滚动分段控件实现问题

需求如下:

  • 实现可横向滚动的分段控件:当选项数量较多时,控件支持横向滚动,每个选项的宽度由内部文本的固有尺寸决定(效果为横向滚动标签栏,文本内容决定选项宽度)
  • 当选项数量较少时,所有选项均匀分配容器的可用宽度,占满整个控件宽度

目前尝试通过自定义Layout实现,但遇到以下核心问题:

  1. 每个分段项由Text和背景视图嵌套在ZStack中,两种场景的布局优先级冲突:滚动场景需要Text优先级更高(宽度由文本决定),均分场景需要背景视图优先级更高(占满分配的宽度)
  2. 若设置Text优先级更高,自定义Layout只能计算出所有选项的最小总宽度,无法获取容器的可用空间大小;若设置背景优先级更高,虽然能拿到容器宽度,但无法确定每个选项在滚动场景下的合适宽度

当前使用的自定义Layout代码(无法正常工作):

struct StretchingHStack: Layout {
    var spacing: CGFloat = 0

    func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
        // Calculate sum of intrinsic widths + spacing, max height
        var totalWidth: CGFloat = 0
        var maxHeight: CGFloat = 0

        for (i, subview) in subviews.enumerated() {
            let size = subview.sizeThatFits(ProposedViewSize(width: .none, height: proposal.height))
            totalWidth += size.width
            maxHeight = max(maxHeight, size.height)
            if i < subviews.count - 1 {
                totalWidth += spacing
            }
        }

        return CGSize(width: totalWidth, height: maxHeight)
    }

    func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
        // Measure intrinsic widths
        let intrinsicWidths = subviews.map {
            $0.sizeThatFits(ProposedViewSize(width: .none, height: bounds.height)).width
        }

        let totalIntrinsicWidth = intrinsicWidths.reduce(0, +) + CGFloat(subviews.count - 1) * spacing
        let extraWidth = max(0, bounds.width - totalIntrinsicWidth)

        // Compute widths per subview:
        // Distribute extra space proportionally to intrinsic widths, or equally if total intrinsic is zero
        // (avoid divide by zero)
        let widths: [CGFloat]

        if totalIntrinsicWidth == 0 {
            let equalWidth = (bounds.width - CGFloat(subviews.count - 1) * spacing) / CGFloat(subviews.count)
            widths = Array(repeating: equalWidth, count: subviews.count)
        } else {
            widths = intrinsicWidths.map { intrinsicWidth in
                intrinsicWidth + (intrinsicWidth / totalIntrinsicWidth) * extraWidth
            }
        }

        // Place subviews horizontally with calculated widths
        var x = bounds.minX

        for (index, subview) in subviews.enumerated() {
            let width = widths[index]
            let height = bounds.height

            let proposal = ProposedViewSize(width: width, height: height)
            let size = subview.sizeThatFits(proposal)

            // Center vertically within bounds.height
            let y = bounds.minY + (bounds.height - size.height) / 2

            subview.place(
                at: CGPoint(x: x, y: y),
                proposal: proposal
            )

            x += width + spacing
        }
    }
}

注:分段项为带背景的文本组件,代码未附上。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 14:55:00