如何使用堆栈视图
SwiftUI 里面的布局控件最常用的三种:VStack、HStack、ZStack。
VStack:视图垂直排列;
HStack:视图横向排列;
ZStack:视图覆盖排列;
它们实际使用效果如下
我们开发下面的一个小案例来加深理解
标题部分
首先创建项目 StackTest
。
我们可以看到标题部分是由两个 Text
组成,它们是上下结构,所以使用 VStack
来包裹它们
VStack() {
Text("会员套餐")
.font(.system(.title))
.fontWeight(.bold)
Text("解锁高级功能")
.font(.title)
.fontWeight(.bold)
}
Stack
视图默认是居中对齐的,如果我们需要改变对齐方式,可以加入 alignment
参数;如果需要撑开点需要加入间距 spacing
参数 | 名称 | 描述 |
---|---|---|
alignment | 居中方式 | .leading 左对齐,.trailing 右对齐,.center 居中 |
spacing | 间距 | 间隔,组件之间的间隔距离 |
VStack(alignment: .leading, spacing: 10) {
Text("会员套餐")
.font(.system(.title))
.fontWeight(.bold)
Text("解锁高级功能")
.font(.title)
.fontWeight(.bold)
}
定价部分
标题部分完成!接下来我们看标题和月份选择区也是上下结构,所以我们也可以在标题部分的 VStack
上再次包裹个 VStack
。
VStack {
VStack(alignment: .leading, spacing: 10) {
Text("会员套餐")
.font(.system(.title))
.fontWeight(.bold)
Text("解锁高级功能")
.font(.title)
.fontWeight(.bold)
}
}
制作定价部分,由设计知道这是三个一样的定价方案样式,我们直接做一个出来,后面复制它就行。
定价方案是由 Text(“连续包月”)
、Text(“¥18”)
组成,依旧可以使用 VStack 来纵向排列
VStack {
Text("连续包月")
.fontWeight(.bold)
.font(.system(size: 17))
.foregroundColor(.gray)
Text("¥18")
.fontWeight(.bold)
.font(.system(size: 30))
.foregroundColor(.red)
}
.padding(20)
.background(Color.orange)
.cornerRadius(10)
除了设置字体,也需要修饰撑开 .padding
、背景色 .background
、圆角 .cornerRadius
可以看到和实际还是有点出入,这是由于没有使用设计的颜色。设置颜色的方式有2种:
一种是使用
RGB
颜色值一种是在
Assets.xcassets
文件中导入颜色再引用
使用 RGB
颜色值的方法
VStack {
Text("连续包月")
.fontWeight(.bold)
.font(.system(size: 17))
.foregroundColor(Color(red: 190/255, green: 188/255, blue: 184/255))
Text("¥18")
.fontWeight(.bold)
.font(.system(size: 30))
.foregroundColor(Color(red: 239/255, green: 129/255, blue: 112/255))
}
.padding(20)
.background(Color(red: 250/255, green: 247/255, blue: 243/255))
.cornerRadius(10)
导入颜色的方法就是:
- 点击
Assets
,在底部点击+
按钮,选择Color set
- 定义一个数值,选中
Any Appearance
,这是默认的颜色,Dark
为黑夜模式下的颜色,Apple 要求上架的应用必须适配黑夜模式。点击左侧顶部“设置”页,点击Color
下的Show Color panel
,这时会打开颜色选项弹窗。我们可以设置RGB
颜色,也可以使用Hex
颜色设置,这里我们设置Hex Color
为faf7f3
。
回到定价方案可以把背景色改成 Color(“faf7f3”)
设计上定价存在一个边框样式,我们可以尝试设置 border
VStack {
Text("连续包月")
.fontWeight(.bold)
.font(.system(size: 17))
.foregroundColor(Color(red: 190/255, green: 188/255, blue: 184/255))
Text("¥18")
.fontWeight(.bold)
.font(.system(size: 30))
.foregroundColor(Color(red: 239/255, green: 129/255, blue: 112/255))
}
.padding(20)
.background(Color("faf7f3"))
.border(Color(red: 202/255, green: 169/255, blue: 106/255),width: 2)
.cornerRadius(10)
但是实际上会出现问题的,因为 SwiftUI
默认为 maskToBounds
,所以这种方式设置的圆角会被裁剪一部分。
所以需要改成
// 定价方案
VStack {
Text("连续包月")
.fontWeight(.bold)
.font(.system(size: 17))
.foregroundColor(Color(red: 190 / 255, green: 188 / 255, blue: 184 / 255))
Text("¥18")
.fontWeight(.bold)
.font(.system(size: 30))
.foregroundColor(Color(red: 239 / 255, green: 129 / 255, blue: 112 / 255))
}
.padding(20)
.background(Color("faf7f3"))
.overlay(RoundedRectangle(cornerRadius: 6).stroke(Color(red: 202 / 255, green: 169 / 255, blue: 106 / 255), lineWidth: 2))
}
.overlay(RoundedRectangle(cornerRadius: 6).stroke(Color(red: 202 / 255, green: 169 / 255, blue: 106 / 255), lineWidth: 2))
这一句代码的含义是:给这个视图加一个圆角,再给圆角描个有颜色的线框。
RoundedRectangle()
:圆角矩形
cornerRadius
:圆角度数
stroke
:描边
Color
:颜色
lineWidth
:线度
第一个定价方案已经完成。
完善定价
看到设计,存在三个同样的定价样式,它们是横向排列,所以可以使用 HStack 包裹它们;复制第一个定价方案,修改文字和去掉不用的边框。
选中 VStack
,按住 command
,点击面板,插入 HStack
查看代码
HStack {
VStack {
Text("连续包月")
.fontWeight(.bold)
.font(.system(size: 17))
.foregroundColor(Color(red: 190/255, green: 188/255, blue: 184/255))
Text("¥18")
.fontWeight(.bold)
.font(.system(size: 30))
.foregroundColor(Color(red: 239/255, green: 129/255, blue: 112/255))
}
.padding(20)
.background(Color("faf7f3"))
.overlay(RoundedRectangle(cornerRadius: 6).stroke(Color(red: 202 / 255, green: 169 / 255, blue: 106 / 255), lineWidth: 2))
VStack {
Text("1个月")
.fontWeight(.bold)
.font(.system(size: 17))
.foregroundColor(Color(red: 190/255, green: 188/255, blue: 184/255))
Text("¥30")
.fontWeight(.bold)
.font(.system(size: 30))
.foregroundColor(Color(red: 239/255, green: 129/255, blue: 112/255))
}
.padding(20)
.background(Color("faf7f3"))
.cornerRadius(6)
VStack {
Text("12个月")
.fontWeight(.bold)
.font(.system(size: 17))
.foregroundColor(Color(red: 190/255, green: 188/255, blue: 184/255))
Text("¥228")
.fontWeight(.bold)
.font(.system(size: 30))
.foregroundColor(Color(red: 239/255, green: 129/255, blue: 112/255))
Text("¥19.00/月")
.fontWeight(.bold)
.font(.system(size: 17))
.foregroundColor(Color(red: 190/255, green: 188/255, blue: 184/255))
}
.padding(20)
.background(Color("faf7f3"))
.cornerRadius(6)
}
我们看到实际上高度不一样了,需要让三个方案一致,需要引入 .frame
修饰;它允许用户定义视图的大小,无论内部元素怎么变化,视图都可以维持固定的大小。
VStack {
Text("12个月")
.fontWeight(.bold)
.font(.system(size: 17))
.foregroundColor(Color(red: 190/255, green: 188/255, blue: 184/255))
Text("¥228")
.fontWeight(.bold)
.font(.system(size: 30))
.foregroundColor(Color(red: 239/255, green: 129/255, blue: 112/255))
Text("¥19.00/月")
.fontWeight(.bold)
.font(.system(size: 17))
.foregroundColor(Color(red: 190/255, green: 188/255, blue: 184/255))
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 90)
.padding(20)
.background(Color("faf7f3"))
.cornerRadius(6)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 90)
设置最小高度固定都是90,最小宽度为0,最大宽度就按照屏幕宽度平分。
看到两边需要留点空间,需要对整个 HStack
设置 .padding
。
HStack {
//...
}
.padding(.horizontal)
可以看到最后一个定价的字体被挤下来了,这里可以使用 .minimumScaleFactor
和 .lineLimit
修饰符修饰
minimumScaleFactor
:缩小字体大小以使文本适合可用空间的最小允许比例lineLimit
:文字行数
VStack {
Text("12个月")
.fontWeight(.bold)
.font(.system(size: 17))
.foregroundColor(Color(red: 190/255, green: 188/255, blue: 184/255))
Text("¥228")
.fontWeight(.bold)
.font(.system(size: 30))
.minimumScaleFactor(0.7)
.lineLimit(1)
.foregroundColor(Color(red: 239/255, green: 129/255, blue: 112/255))
Text("¥19.00/月")
.fontWeight(.bold)
.font(.system(size: 17))
.minimumScaleFactor(0.7)
.lineLimit(1)
.foregroundColor(Color(red: 190/255, green: 188/255, blue: 184/255))
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 90)
.padding(20)
.background(Color("faf7f3"))
.cornerRadius(6)
定位
第一个定价方案的顶部有个“首月特惠”,它是覆盖在视图上面而且覆盖了一半。
找到第一个定价,在 VStack
上包裹 ZStack
,完善”首月特惠“的代码;调整文本的位置,使用 .offset
修饰符,它设置偏移量;简单来说,就是修饰被选定的视图,应该移动到哪里。
X
,Y
分别对应坐标轴位置,X
轴正数为右移,负数为左移,Y
轴正数下移,负数上移。
ZStack {
VStack {
// ...
}
Text("首月特惠")
.font(.system(size: 14))
.fontWeight(.bold)
.foregroundColor(.white)
.padding(5)
.background(Color(red: 202 / 255, green: 169 / 255, blue: 106 / 255))
.cornerRadius(4)
.offset(x: 0, y: -65)
}
完善代码
对照设计,标题内容是左对齐的,但现在仍是居中对齐,我们希望整个文字和定价方案是左对齐,需要引入一个新的参数,叫做 Spacer()
。
Spacer()
相当于空白视图,什么都没有,但可以在 Stack
中充当“挤开”其他视图的作用。
标题部分和 Spacer()
是横向排列,需要把整个标题包裹在一个 HStack
里
HStack {
VStack(alignment: .leading, spacing: 10) {
Text("会员套餐")
.font(.system(.title))
.fontWeight(.bold)
Text("解锁高级功能")
.font(.title)
.fontWeight(.bold)
}
// 间隔
Spacer()
}
.padding()
最终我们就完成了整个视图的开发。
总结
最后,我们总结下用到的一些修饰符
整体布局使用 VStack、HStack、ZStack 来包裹
设置字体颜色,大小
设置边框
.stroke
与圆角.cornerRadius
.overlay(RoundedRectangle(cornerRadius: 6).stroke(Color(red: 202 / 255, green: 169 / 255, blue: 106 / 255), lineWidth: 2))
- 文字最小行数
Text("¥19.00/月")
// ...
.minimumScaleFactor(0.7)
.lineLimit(1)
- 定位
offset
ZStack {
Text("首月特惠")
.font(.system(size: 14))
.fontWeight(.bold)
.foregroundColor(.white)
.padding(5)
.background(Color(red: 202 / 255, green: 169 / 255, blue: 106 / 255))
.cornerRadius(4)
.offset(x: 0, y: -65)
}