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强制盒子保持正方形:
- 修改容器视图:移除
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) // 可选:添加左右内边距避免盒子贴屏幕边缘 } }
- 调整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获取父容器宽度,手动计算盒子尺寸:
- 修改容器视图:用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) // 左右内边距 } } }
- 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
相关产品推荐
相关产品推荐

