我报名参加金石计划1期应战——分割10万奖池,这是我的第2篇文章,点击检查活动概况。
swiftui 中最常用的构建布局的视图当属:VStack、HStack、ZStack 莫属。
凭借这 3 个 Stack,咱们能构建出,上下,左右,等宽,叠放,或者是类似于前端的 Flex 布局;下面让咱们来看看这 3 个利器的具体运用。
VStack
VStack 用于垂直摆放视图。
struct Test: View {
var body: some View {
VStack{
Text("高档会员").padding(5).font(.headline).foregroundColor(Color(hex: "#8E6A30"))
Divider()
Text("无限制账户").padding(.vertical, 5)
Text("币种切换,主动核算汇率").padding(.vertical, 5)
Text("价格主动更新").padding(.vertical, 5)
Text("云端数据同步").padding(.vertical, 5)
Text("专属布景皮肤").padding(.vertical, 5)
}
.foregroundColor(Color(hex: "#8E6A30"))
}
}
在上面的代码中,咱们将文本在垂直线上摆放,同时给 VStack 添加了 foregroundColor 修饰符,这会使 VStack 视图内的文本运用同一种色彩。
对齐方向
VStack 还支撑对内部视图进行对齐操控。
VStack 支撑三种对齐方法,默许运用居中对齐:
- .leading:左对齐
- .trailing:右对齐
- .center:居中对齐
需求留意的是,VStack 设置的对齐方向是水平方向。
struct Test: View {
var body: some View {
VStack(alignment: .leading){
Text("高档会员").padding(5).font(.headline).foregroundColor(Color(hex: "#8E6A30"))
Divider()
Text("无限制账户").padding(.vertical, 5)
Text("币种切换,主动核算汇率").padding(.vertical, 5)
Text("价格主动更新").padding(.vertical, 5)
Text("云端数据同步").padding(.vertical, 5)
Text("专属布景皮肤").padding(.vertical, 5)
}
.foregroundColor(Color(hex: "#8E6A30"))
}
}
设置距离
在上面例子中,咱们对每个文本设置了垂直方向的 padding,也就是上下边距分别为 5 来使文本不至于紧挨在一起。但这种设置方法太多余繁琐,咱们能够凭借 VStack 的第二个属性 spacing 来设置内部子视图的上下距离。
struct Test: View {
var body: some View {
VStack(alignment: .leading, spacing: 15){
Text("高档会员").padding(5).font(.headline).foregroundColor(Color(hex: "#8E6A30"))
Divider()
Text("无限制账户")
Text("币种切换,主动核算汇率")
Text("价格主动更新")
Text("云端数据同步")
Text("专属布景皮肤")
}
.foregroundColor(Color(hex: "#8E6A30"))
}
}
留意:这儿 spacing 的值为 15,作用近似于 padding(.vertical, 5)。
HStack
HStack 用于将内部子视图摆放在水平方向上。
import SwiftUI
struct Test: View {
var body: some View {
HStack{
Text("高档会员").font(.headline).foregroundColor(Color(hex: "#8E6A30"))
Divider()
Text("无限制账户")
Text("币种切换,主动核算汇率")
Text("价格主动更新")
Text("云端数据同步")
Text("专属布景皮肤")
}
.foregroundColor(Color(hex: "#8E6A30"))
}
}
struct Test_Previews: PreviewProvider {
static var previews: some View {
Test()
}
}
HStack 同样能够设置内部子视图的对齐方法和距离,但其设置的对齐方向和距离和 VStack 相反,是针对垂直方向的。
HStack 的 spacing 运用方法和 VStack 的相同,这儿不再赘述。
HStack 支撑五种对齐方法,默许运用居中对齐:
- .top:左顶部对齐
- .bottom:底部对齐
- .center:居中对齐
- .firstTextBaseline:根据第一行文本的基线对齐
- .lastTextBaseline:根据最终一行文本的基线对齐
来看一个具体示例:
import SwiftUI
struct Test: View {
var body: some View {
VStack {
HStack(alignment: .top){
Text("顶部对齐").font(.headline).foregroundColor(Color(hex: "#8E6A30"))
Divider()
Text("无限制账户")
Text("币种切换,主动核算汇率")
Text("价格主动更新")
.padding(.vertical, 5)
Text("云端数据同步")
.padding(.vertical, 5)
Text("专属布景皮肤")
}
.foregroundColor(Color(hex: "#8E6A30"))
HStack(alignment: .center){
Text("居中对齐").font(.headline).foregroundColor(Color(hex: "#8E6A30"))
Divider()
Text("无限制账户")
Text("币种切换,主动核算汇率")
Text("价格主动更新")
.padding(.vertical, 5)
Text("云端数据同步")
.padding(.vertical, 5)
Text("专属布景皮肤")
}
.foregroundColor(Color(hex: "#8E6A30"))
HStack(alignment: .bottom){
Text("底部对齐").font(.headline).foregroundColor(Color(hex: "#8E6A30"))
Divider()
Text("无限制账户")
Text("币种切换,主动核算汇率")
Text("价格主动更新")
.padding(.vertical, 5)
Text("云端数据同步")
.padding(.vertical, 5)
Text("专属布景皮肤")
}
.foregroundColor(Color(hex: "#8E6A30"))
HStack(alignment: .firstTextBaseline){
Text("第一行文本基线对齐").font(.headline).foregroundColor(Color(hex: "#8E6A30"))
Divider()
Text("无限制账户")
Text("币种切换,主动核算汇率")
Text("价格主动更新")
.padding(.vertical, 5)
Text("云端数据同步")
.padding(.vertical, 5)
Text("专属布景皮肤")
}
.foregroundColor(Color(hex: "#8E6A30"))
HStack(alignment: .lastTextBaseline){
Text("最终一行文本基线对齐").font(.headline).foregroundColor(Color(hex: "#8E6A30"))
Divider()
Text("无限制账户")
Text("币种切换,主动核算汇率")
Text("价格主动更新")
.padding(.vertical, 5)
Text("云端数据同步")
.padding(.vertical, 5)
Text("专属布景皮肤")
}
.foregroundColor(Color(hex: "#8E6A30"))
}
}
}
struct Test_Previews: PreviewProvider {
static var previews: some View {
Test()
}
}
ZStack
ZStack 主要用于将内部子视图在 Z 轴上摆放,其特点是对于内部的接连子视图,都会分配一个比前一个子视图优先级更高的 Z 轴值,也就是,越在后边出现的子视图,越会显现在“前”。
import SwiftUI
struct Test: View {
let colors: [Color] =
[.red, .orange, .yellow, .green, .blue, .purple]
var body: some View {
ZStack {
ForEach(0..<colors.count) {
Rectangle()
.fill(colors[$0])
.frame(width: 100, height: 100)
.offset(x: CGFloat($0) * 10.0,
y: CGFloat($0) * 10.0)
}
}
}
}
struct Test_Previews: PreviewProvider {
static var previews: some View {
Test()
}
}
再看一个示例:
import SwiftUI
struct Test: View {
let colors: [Color] =
[.red, .orange, .yellow, .green, .blue, .purple]
var body: some View {
ZStack {
Image(systemName: "gamecontroller.fill")
.resizable()
.aspectRatio(contentMode: .fit)
HStack {
VStack(alignment: .leading) {
Text("Game Play")
.font(.headline)
Text("Go! Go! Go!")
.font(.subheadline)
}
Spacer()
}
.padding()
.foregroundColor(.primary)
.background(Color.primary
.colorInvert()
.opacity(0.75))
}.background(Color.gray)
}
}
struct Test_Previews: PreviewProvider {
static var previews: some View {
Test()
}
}
微信读书会员页面仿写
介绍完 3 个布局神器,下面让咱们来做一个复杂点的示例。
下图是微信读书会员卡页面,让咱们用 3个 Stack 来模仿这个页面。
- 先设置基本布局
会员卡页面的3个主要的布局内容包含:顶部导航、中心的翻滚内容区域、底部固定区域,咱们先把这3个区域的布局设置好。
import SwiftUI
struct Test: View {
var body: some View {
NavigationView {
ZStack {
Color(red: 39/255, green: 46/255, blue: 71/255).edgesIgnoringSafeArea(.all)
ScrollView(showsIndicators: false) {
}
}
.safeAreaInset(edge: .bottom) {
}
.navigationBarTitleDisplayMode(.inline)
}
}
}
NavigationView 用于设置导航栏;ZStack 用于设置底层的布景色,其他视图将会在布景色之上显现;safeAreaInset 用于设置底部固定区域。
- 主要修饰符
在开发的过程中咱们会用到大量的 swiftui 修饰符,修饰符的运用次序是会影响布局的结果的哦,下面是用到的一些主要修饰符:
- .padding: 边距。
- foregroundColor:设置视图色彩,比如文本、图标的色彩。
- font:设置文字大小。
- .frame:设置视图宽高。
- .background:设置视图布景色。
- .cornerRadius:设置圆角。
- .safeAreaInset:设置一个安全区域,也就是固定区域。
- .bold:设置文字字重,需求 ios16。
- .navigationBarTitleDisplayMode:设置导航栏形式
- .toolbar:自定义导航栏内容。
- .navigationBarItems:设置导航栏左右两头按钮。
- .overlay:这儿用于设置圆角边框。
- 完好代码示例
直接上完好代码,详细内容请看代码内注释。
import SwiftUI
struct Test: View {
var body: some View {
NavigationView {
ZStack {
// 设置页面布景色
Color(red: 39/255, green: 46/255, blue: 71/255).edgesIgnoringSafeArea(.all)
ScrollView(showsIndicators: false) {
VStack(alignment: .leading) {
VStack(alignment: .leading) {
Text("付费会员卡")
.bold()
.padding(.bottom, 4)
.foregroundColor(Color(red: 231/255, green: 200/255, blue: 153/255))
}
// 填充中心空白区域,使文字上下靠边
Spacer()
HStack(alignment: .bottom) {
Text("3").bold().padding(.bottom, -4).font(.system(size: 24))
Text("天9月27日到期").font(.system(size: 10))
// 空白区域填充,使文字居左
Spacer(minLength: 0)
}.foregroundColor(Color(red: 231/255, green: 200/255, blue: 153/255))
}
.padding()
.frame(height: 180)
// 会员卡布景色突变
.background(RadialGradient(
gradient: Gradient(
colors: [
Color(red: 56/255, green: 81/255, blue: 116/255),
Color(red: 39/255, green: 46/255, blue: 71/255),
Color(red: 231/255, green: 200/255, blue: 153/255),
Color(red: 39/255, green: 46/255, blue: 71/255),
]
),
center: .center,
startRadius: 2,
endRadius: 650)
)
// 圆角边框
.overlay(
RoundedRectangle(cornerRadius: 12, style: .continuous)
.stroke(Color(red: 231/255, green: 200/255, blue: 153/255), lineWidth: 1)
).padding(.bottom, 10)
VStack(alignment: .leading) {
HStack(alignment: .top, spacing: 0) {
VStack(alignment: .leading, spacing: 20) {
HStack() {
Image(systemName: "infinity")
Text("付费会员卡").bold()
}.padding(.bottom, -5)
Divider().overlay(Color.gray)
Text("全场出版书畅读")
Text("全场有声书畅听")
Text("书架无上限")
Text("离线下载无上限")
Text("时长可兑换体会卡和书币")
Text("专属阅读布景和字体")
}
.padding()
.foregroundColor(Color(red: 231/255, green: 200/255, blue: 153/255))
Spacer()
VStack(alignment: .leading, spacing: 20) {
HStack() {
Image(systemName: "infinity")
Text("体会卡")
}.padding(.bottom, -5)
Divider().overlay(Color.gray)
Text("部分出版书畅读")
Text("仅可收盘 AI 朗读")
Text("书架 500 本上限")
Text("每月可下载 3 本")
Text("仅可兑换体会卡")
Text("-")
}
.padding()
.foregroundColor(Color.gray)
}.font(.system(size: 12))
}
.background(Color(red: 47/255, green: 54/255, blue: 77/255))
.cornerRadius(12)
}.padding([.top, .leading, .trailing])
}
// 设置一个底部固定区域,然后自定义其内部子视图
.safeAreaInset(edge: .bottom) {
VStack() {
VStack() {
HStack {
VStack(alignment: .leading) {
Text("接连包月 19.00").bold().padding(.bottom, 6)
Text("19元/月-主动续费可随时取消").font(.system(size: 10))
}
Spacer()
Text("立即开通")
.font(.system(size: 14))
.bold()
.padding(EdgeInsets(top: 6, leading: 20, bottom: 6, trailing: 20))
.foregroundColor(Color(hex: "#6F5021"))
.background(Color(hex: "#EACEA6"))
.cornerRadius(16)
}.foregroundColor(Color(hex: "#6F5021"))
}
.padding()
// 布景线性突变,从左到右
.background(LinearGradient(gradient: Gradient(colors: [Color(hex: "#E7C899"), Color(hex: "#F9E9CF")]), startPoint: .leading, endPoint: .trailing))
.cornerRadius(12)
.padding(.bottom, 10)
VStack(alignment: .leading) {
HStack {
VStack(alignment: .leading) {
Text("购买年卡").padding(.bottom, 1)
HStack {
Text("228.00").font(.headline)
Text("(19元/月)").font(.subheadline)
}
}.frame(maxWidth: .infinity).font(.system(size: 14)) .foregroundColor(Color(red: 231/255, green: 200/255, blue: 153/255)).padding().background(Color(red: 52/255, green: 58/255, blue: 78/255)).cornerRadius(12)
HStack {
Image(systemName: "gift").font(.system(size: 20))
VStack(alignment: .leading) {
Text("赠送年卡给好友").padding(.bottom, 1)
VStack(alignment: .leading) {
Text("228.00").font(.headline)
}
}
}.frame(maxWidth: .infinity).font(.system(size: 14)) .foregroundColor(Color(red: 231/255, green: 200/255, blue: 153/255)).padding().background(Color(red: 52/255, green: 58/255, blue: 78/255)).cornerRadius(12)
}
HStack {
VStack(alignment: .leading) {
Text("购买季卡").padding(.bottom, 1)
HStack {
Text("60.00").font(.headline)
Text("(20元/月)").font(.subheadline)
}
}.frame(maxWidth: .infinity).font(.system(size: 14)) .foregroundColor(Color(red: 231/255, green: 200/255, blue: 153/255)).padding().background(Color(red: 52/255, green: 58/255, blue: 78/255)).cornerRadius(12)
VStack(alignment: .leading) {
Text("购买月卡").padding(.bottom, 1)
VStack(alignment: .leading) {
Text("30.00").font(.headline)
}
}.frame(maxWidth: .infinity).font(.system(size: 14)) .foregroundColor(Color(red: 231/255, green: 200/255, blue: 153/255)).padding().background(Color(red: 52/255, green: 58/255, blue: 78/255)).cornerRadius(12)
}
Text("确认购买后,将向您的 iTunes 账户收款。购买接连包月项目,将主动续订,iTunes 账户会在到期前 24 小时内扣费。在此之前,您能够在系统[设置] -> [iTunes Store 与 App Store] -> [Apple ID] 里面进行退订。").font(.system(size: 10)).foregroundColor(Color.gray).padding(.top, 10)
}
}
.padding()
.background(
Color(red: 41/255, green: 50/255, blue: 75/255)
)
}
// 设置导航栏为行内形式
.navigationBarTitleDisplayMode(.inline)
// 自定义导航栏标题内容
.toolbar {
ToolbarItem(placement: .principal) {
VStack {
Text("会员卡").font(.headline).padding(.bottom, 2)
Text("已运用 517 天累计节省 839.76 元").font(.system(size: 12))
}.foregroundColor(Color(red: 231/255, green: 200/255, blue: 153/255))
}
}
// 自定义导航栏左右两头的按钮
.navigationBarItems(
leading: Button(action: {
// 点击按钮时的操作
}, label: {
Image(systemName: "chevron.left")
.foregroundColor(Color(red: 231/255, green: 200/255, blue: 153/255))
}), trailing: Button(action: {
// 点击按钮时的操作
}, label: {
Text("明细")
.foregroundColor(Color(red: 231/255, green: 200/255, blue: 153/255))
}))
}
}
}
struct Test_Previews: PreviewProvider {
static var previews: some View {
Test()
}
}
总结
咱们学习了 VStack、HStack、ZStack 3个构建布局的重要东西,运用它们及其其他的视图和修饰符,完成了一个微信读书会员卡页面,这个页面包含了许多常见的布局方法和 swiftui 开发中常用的修饰符和控件,相信经过本文的学习,你现已掌握了不少技巧。大家还用 swiftui 开发出了哪些美观的页面呢,欢迎在评论区晒出你的作品。
这是 swiftui 开发之旅专栏的文章,是 swiftui 开发学习的经验总结及实用技巧共享,欢迎重视该专栏,会坚持输出。同时欢迎重视我的个人大众号 @JSHub:提供最新的开发信息速报,优质的技术干货引荐。
点赞:如果有收成和帮助,请点个赞支撑一下!
保藏:欢迎保藏文章,随时检查!
评论:欢迎评论交流学习,共同进步!