SwiftUI中根据空间自适应的自定义分段控件布局实现问题
SwiftUI 可滚动分段控件实现问题
需求如下:
- 实现可横向滚动的分段控件:当选项数量较多时,控件支持横向滚动,每个选项的宽度由内部文本的固有尺寸决定(效果为横向滚动标签栏,文本内容决定选项宽度)
- 当选项数量较少时,所有选项均匀分配容器的可用宽度,占满整个控件宽度
目前尝试通过自定义Layout实现,但遇到以下核心问题:
- 每个分段项由
Text和背景视图嵌套在ZStack中,两种场景的布局优先级冲突:滚动场景需要Text优先级更高(宽度由文本决定),均分场景需要背景视图优先级更高(占满分配的宽度) - 若设置
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
相关产品推荐
相关产品推荐

