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

SwiftUI中HStack与VStack对齐及矩形宽度适配问题求助

问题描述

我尝试在ScrollView中精确放置3个矩形,其中2个横向排列、1个纵向排列,要求三个矩形宽度完全一致。目前横向排列的矩形在各设备上适配正常,但纵向的单个矩形无法达到与横向矩形相同的宽度效果。

矩形宽度不一致效果

原代码如下:

var body: some View {

        ScrollView(.vertical) {
            VStack(spacing: 32) {
                Spacer()
                                
                VStack {
                    HStack {
                        ForEach(1...2, id: \.self) { cards in
                            ZStack(alignment: .center) {
                                RoundedRectangle(cornerRadius: 10)
                                    .stroke(lineWidth: 0.5)
                                VStack(alignment: .center, spacing: 16) {
                                    Image(systemName: "megaphone")
                                    Text("Text")
                                    Text("Text")
                                        .multilineTextAlignment(.center)
                                    Image(systemName: "circle")
                                }
                                .padding()
                            }
                        }
                    }

                    ZStack(alignment: .center) {
                        RoundedRectangle(cornerRadius: 10)
                            .stroke(lineWidth: 0.5)
                        VStack(alignment: .center, spacing: 16) {
                            Image(systemName: "megaphone")
                            Text("Text")
                            Text("Text")
                                .multilineTextAlignment(.center)
                            Image(systemName: "circle")
                        }
                        .padding()
                    }
                }
            }
        }
    }
问题原因与解决方法

当前写法的核心问题是:横向的两个矩形在HStack中会自动均分父布局的可用宽度,但单个纵向的矩形默认会根据内部内容自适应宽度,不会主动匹配横向矩形的宽度。

以下是两种可靠的修复方案:

方案一:用frame(maxWidth: .infinity)统一约束宽度

给每个卡片容器添加frame(maxWidth: .infinity),让它们都占满所在布局的可用宽度。HStack会自动将空间均分给两个子视图,纵向卡片设置同样的frame后,宽度会和横向单个卡片完全一致。修改后的代码如下:

var body: some View {
    ScrollView(.vertical) {
        VStack(spacing: 32) {
            Spacer()
            
            VStack {
                HStack {
                    ForEach(1...2, id: \.self) { _ in
                        cardView()
                            .frame(maxWidth: .infinity) // 让横向卡片占满HStack分配的宽度
                    }
                }
                
                cardView()
                    .frame(maxWidth: .infinity) // 让纵向卡片占满父布局的可用宽度
            }
        }
    }
}

// 提取重复视图为单独组件,减少代码冗余
private func cardView() -> some View {
    ZStack(alignment: .center) {
        RoundedRectangle(cornerRadius: 10)
            .stroke(lineWidth: 0.5)
        VStack(alignment: .center, spacing: 16) {
            Image(systemName: "megaphone")
            Text("Text")
            Text("Text")
                .multilineTextAlignment(.center)
            Image(systemName: "circle")
        }
        .padding()
    }
}

方案二:用GeometryReader获取并复用横向卡片宽度

如果需要更精确的控制,可以通过GeometryReader读取横向卡片的宽度,再将该宽度应用到纵向卡片上:

import SwiftUI

struct ContentView: View {
    @State private var cardWidth: CGFloat = 0
    
    var body: some View {
        ScrollView(.vertical) {
            VStack(spacing: 32) {
                Spacer()
                
                VStack {
                    HStack {
                        ForEach(1...2, id: \.self) { _ in
                            cardView()
                                .frame(maxWidth: .infinity)
                                .background(GeometryReader { proxy in
                                    Color.clear
                                        .preference(key: CardWidthKey.self, value: proxy.size.width)
                                })
                        }
                    }
                    .onPreferenceChange(CardWidthKey.self) { newWidth in
                        cardWidth = newWidth
                    }
                    
                    cardView()
                        .frame(width: cardWidth) // 复用横向卡片的宽度
                }
            }
        }
    }
    
    private func cardView() -> some View {
        ZStack(alignment: .center) {
            RoundedRectangle(cornerRadius: 10)
                .stroke(lineWidth: 0.5)
            VStack(alignment: .center, spacing: 16) {
                Image(systemName: "megaphone")
                Text("Text")
                Text("Text")
                    .multilineTextAlignment(.center)
                Image(systemName: "circle")
            }
            .padding()
        }
    }
}

// 自定义PreferenceKey用于传递宽度值
private struct CardWidthKey: PreferenceKey {
    static var defaultValue: CGFloat = 0
    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
        value = nextValue()
    }
}

注意事项

  • 优先选择方案一,代码更简洁,适配性更强,能自动适配不同设备的屏幕尺寸。
  • 提取重复的卡片视图为单独组件,便于后续统一修改样式和逻辑,避免重复代码。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 15:39:59