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

SwiftUI如何实现两个等大正方形占满屏幕宽度(含间距)

问题描述

我想要制作订阅价格信息框,如何让这些盒子成为正方形并占满屏幕宽度(扣除间距)?
(明确说明:我关注的是如何让两个盒子成为正方形,且高度自动适配屏幕尺寸,完整设计将在尺寸实现后处理)

以下是我目前尝试的代码:

单个盒子的代码:

struct SubscriptionPriceBox: View {
let title: String
let duration: String
let price: String
let renewInfo: String

var body: some View {
    ZStack(alignment: .top) {
        VStack {
            VStack {
                Text(duration)
                Text(price)
            }
            Text(renewInfo)
        }
        .overlay {
            RoundedRectangle(cornerRadius: 2)
                .stroke(.gray, lineWidth: 2)
        }
        .aspectRatio(1, contentMode: .fit)
        .frame(maxWidth: .infinity)
        
        Text(title)
            .background(Color.yellow)
            .offset(x: -20, y: -10)
    }
    .background(Color.red)
}

容器视图的代码:

var body: some View {
    VStack {
            HStack(alignment: .center) {
            SubscriptionPriceBox(title: "Basic Price",
                                 duration: "Monthly",
                                 price: "2.49$ / month",
                                 renewInfo: "Renew every month")
            SubscriptionPriceBox(title: "Save 33%",
                                 duration: "Annual",
                                 price: "19.99$ / month",
                                 renewInfo: "Renew every year")
        }.fixedSize(horizontal: true, vertical: false)

我认为GeometryReader是一种可行方案,但未能完全实现。同时我也想了解无需使用GeometryReader的实现方式。


解决方案

一、无需使用GeometryReader的实现方式

核心思路是利用SwiftUI的布局自动分配机制,结合aspectRatio强制盒子保持正方形:

  1. 修改容器视图:移除fixedSize(horizontal: true),给HStack设置间距,并让每个子视图占满分配的宽度:
var body: some View {
    VStack {
        HStack(alignment: .center, spacing: 16) { // 自定义盒子间的间距
            SubscriptionPriceBox(title: "Basic Price",
                                 duration: "Monthly",
                                 price: "2.49$ / month",
                                 renewInfo: "Renew every month")
            .frame(maxWidth: .infinity) // 让盒子占满HStack分配的宽度
            
            SubscriptionPriceBox(title: "Save 33%",
                                 duration: "Annual",
                                 price: "19.99$ / month",
                                 renewInfo: "Renew every year")
            .frame(maxWidth: .infinity)
        }
        .padding(.horizontal, 16) // 可选:添加左右内边距避免盒子贴屏幕边缘
    }
}
  1. 调整SubscriptionPriceBox布局:将aspectRatio(1, contentMode: .fit)移到ZStack层级,确保整个盒子保持正方形:
struct SubscriptionPriceBox: View {
let title: String
let duration: String
let price: String
let renewInfo: String

var body: some View {
    ZStack(alignment: .top) {
        VStack(spacing: 12) { // 增加内部元素间距优化排版
            VStack(spacing: 4) {
                Text(duration)
                Text(price)
                    .font(.title) // 可选:放大价格突出显示
            }
            Text(renewInfo)
                .font(.caption)
        }
        .overlay {
            RoundedRectangle(cornerRadius: 2)
                .stroke(.gray, lineWidth: 2)
        }
        
        Text(title)
            .background(Color.yellow)
            .offset(x: -20, y: -10)
    }
    .background(Color.red)
    .aspectRatio(1, contentMode: .fit) // 强制整个盒子宽高相等,形成正方形
}
}

设置完成后,HStack会自动平分屏幕宽度(扣除间距和内边距),每个盒子的宽度由HStack分配,aspectRatio(1)会让高度等于宽度,完美形成正方形。

二、使用GeometryReader的实现方式

如果需要更精确的尺寸控制,可通过GeometryReader获取父容器宽度,手动计算盒子尺寸:

  1. 修改容器视图:用GeometryReader包裹HStack,计算每个盒子的正方形尺寸:
var body: some View {
    VStack {
        GeometryReader { geo in
            // 计算单个盒子宽度:总宽度 - 盒子间距 - 左右内边距,再平分
            let boxSize = (geo.size.width - 16 - 32) / 2
            HStack(alignment: .center, spacing: 16) {
                SubscriptionPriceBox(title: "Basic Price",
                                     duration: "Monthly",
                                     price: "2.49$ / month",
                                     renewInfo: "Renew every month")
                .frame(width: boxSize, height: boxSize) // 设置固定正方形尺寸
                
                SubscriptionPriceBox(title: "Save 33%",
                                     duration: "Annual",
                                     price: "19.99$ / month",
                                     renewInfo: "Renew every year")
                .frame(width: boxSize, height: boxSize)
            }
            .padding(.horizontal, 16) // 左右内边距
        }
    }
}
  1. SubscriptionPriceBox保持原有结构:无需额外设置aspectRatio,因为frame已经明确宽高相等:
struct SubscriptionPriceBox: View {
let title: String
let duration: String
let price: String
let renewInfo: String

var body: some View {
    ZStack(alignment: .top) {
        VStack(spacing: 12) {
            VStack(spacing: 4) {
                Text(duration)
                Text(price)
                    .font(.title)
            }
            Text(renewInfo)
                .font(.caption)
        }
        .overlay {
            RoundedRectangle(cornerRadius: 2)
                .stroke(.gray, lineWidth: 2)
        }
        
        Text(title)
            .background(Color.yellow)
            .offset(x: -20, y: -10)
    }
    .background(Color.red)
}
}

这种方式适合需要精确适配特定边距、或结合复杂布局逻辑的场景。


内容的提问来源于stack exchange,提问作者haven-way

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 14:54:20