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

SwiftUI动态字体适配:按需切换Spacer与ScrollView方案

SwiftUI:动态切换HStack填充/横向滚动布局(适配动态字体)

问题根源

当HStack被包裹在横向ScrollView中时,ScrollView的内容容器默认仅适配内容宽度,导致Spacer无法拉伸HStack占满可用空间;而直接用GeometryReader获取宽度时,在垂直ScrollView中会因GeometryReader的收缩特性导致子视图高度异常。

解决方案:基于尺寸检测的自适应布局

通过自定义PreferenceKey检测内容宽度与可用宽度,动态切换普通HStack(带Spacer填充)或横向ScrollView布局,同时避免GeometryReader的高度问题。

1. 定义尺寸检测工具

import SwiftUI

// 用于传递视图尺寸的PreferenceKey
struct SizePreferenceKey: PreferenceKey {
    static var defaultValue: CGSize = .zero
    static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
        value = nextValue()
    }
}

// 扩展View,方便读取自身尺寸
extension View {
    func readSize(onChange: @escaping (CGSize) -> Void) -> some View {
        background(
            GeometryReader { proxy in
                Color.clear
                    .preference(key: SizePreferenceKey.self, value: proxy.size)
            }
        )
        .onPreferenceChange(SizePreferenceKey.self, perform: onChange)
    }
}

2. 封装自适应HStack组件

struct AdaptiveHStack<Content: View>: View {
    let spacing: CGFloat
    let content: () -> Content
    
    @State private var contentWidth: CGFloat = 0
    @State private var availableWidth: CGFloat = 0
    
    // 提供默认spacing的初始化方法
    init(spacing: CGFloat = 0, @ViewBuilder content: @escaping () -> Content) {
        self.spacing = spacing
        self.content = content
    }
    
    var body: some View {
        ZStack(alignment: .topLeading) {
            // 不可见的占位视图,用于获取当前可用宽度
            Color.clear
                .frame(maxWidth: .infinity)
                .readSize { size in
                    availableWidth = size.width
                }
            
            // 根据尺寸对比切换布局
            if contentWidth <= availableWidth {
                HStack(spacing: spacing, content: content)
                    .frame(maxWidth: .infinity)
                    .readSize { size in
                        contentWidth = size.width
                    }
            } else {
                ScrollView(.horizontal) {
                    HStack(spacing: spacing, content: content)
                        .readSize { size in
                            contentWidth = size.width
                        }
                }
                .scrollIndicators(.hidden) // 可根据需求显示/隐藏滚动指示器
            }
        }
    }
}

3. 使用示例

struct ScrollViewSandbox: View {
    var body: some View {
        ScrollView {
            AdaptiveHStack(spacing: 20) {
                VStack {
                    Text("Line 1")
                    Text("Line 2")
                }
                Spacer()
                VStack {
                    Text("Line 1")
                    Text("Line 2")
                }
                Spacer()
                VStack {
                    Text("Line 1")
                    Text("Line 2")
                }
            }
            .padding()
            
            Text("Another line")
                .padding()
        }
    }
}

#Preview {
    ScrollViewSandbox()
}

方案说明

  • 尺寸检测:通过readSize扩展获取可用宽度(ZStack中的透明Color占满父容器)和HStack的实际内容宽度
  • 动态切换:当内容宽度≤可用宽度时,用frame(maxWidth: .infinity)让HStack占满空间,Spacer正常工作;内容过宽时自动启用横向ScrollView
  • 高度适配:避免了GeometryReader直接包裹带来的高度收缩问题,组件高度由内部内容自然决定

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 06:32:47