在你已经了解了SwiftUI的基础知识并掌握了如何显示文本内容后,现在是时候学习如何布局用户界面了。
上一篇 SwiftUI 图片视图 介绍了 SwiftUI 中使用 Image 显示图片。
今天我们介绍 SwiftUI 中使用 VStack、HStack 和 ZStack 进行布局。
在本教程中,我们将深入探讨SwiftUI中的堆栈布局,包括VStack、HStack和ZStack,
并展示如何通过这些堆栈构建复杂的网格布局。通过逐步构建一个简单的网格界面,你将学会如何有效地组合视图,
创建出理想的用户界面。
目录
- 什么是SwiftUI中的堆栈
- 理解VStack、HStack和ZStack
- 创建一个新的SwiftUI项目
- 使用VStack
- 使用HStack
- 提取视图以组织代码
- 使用ZStack
- 使用Spacer进行布局调整
- 练习:创建一个网格布局
- 总结
什么是SwiftUI中的堆栈
堆栈(Stacks)是SwiftUI中用于组合视图的核心组件。通过在水平(HStack)和垂直(VStack)堆栈中组合视图, 你可以构建出复杂且响应式的用户界面。在UIKit中,构建适应不同屏幕尺寸的界面通常需要使用自动布局(Auto Layout), 这对于初学者来说可能较为复杂。而在SwiftUI中,堆栈简化了这一过程,使得布局更加直观和易于掌握。
理解VStack、HStack和ZStack
SwiftUI提供了三种主要的堆栈类型,帮助开发者以不同的方向组合视图:
VStack:垂直堆叠视图,将子视图按垂直方向排列。HStack:水平堆叠视图,将子视图按水平方向排列。ZStack:叠加堆叠视图,将子视图层层叠加在一起。
下图展示了这三种堆栈如何组织视图:
创建一个新的SwiftUI项目
首先,启动Xcode并创建一个新的项目:
- 打开Xcode,选择创建一个新的Xcode项目。
- 在iOS类别下选择App模板,然后点击下一步。
- 输入项目名称,例如
SwiftUIStacks。组织名称可以填写你的公司或组织名称,如com.yourcompany。 - 在用户界面(User Interface)选项中选择SwiftUI,语言选择Swift。
- 点击下一步,选择一个文件夹来保存项目。
创建项目后,Xcode将自动加载ContentView.swift文件并显示设计/预览画布。
如果看不到预览,可以通过Xcode菜单选择Editor > Canvas来启用。
使用VStack
让我们从构建一个简单的垂直堆叠视图开始。假设我们要显示一个标题和副标题,可以使用VStack来垂直排列两个Text视图。
示例:创建一个垂直堆叠视图
struct ContentView: View {
var body: some View {
VStack {
Text("Choose")
.font(.system(.largeTitle, design: .rounded))
.fontWeight(.black)
Text("Your Plan")
.font(.system(.largeTitle, design: .rounded))
.fontWeight(.black)
}
}
}
调整对齐方式和间距
默认情况下,VStack中的视图是居中的。要将它们左对齐,并调整视图之间的间距,可以使用alignment和spacing参数:
VStack(alignment: .leading, spacing: 2) {
Text("Choose")
.font(.system(.largeTitle, design: .rounded))
.fontWeight(.black)
Text("Your Plan")
.font(.system(.largeTitle, design: .rounded))
.fontWeight(.black)
}
这样,两个文本视图将垂直排列,并且左对齐,间距为2点。
使用HStack
接下来,我们将布局两个价格计划:Basic和Pro。这两个计划的布局非常相似,因此可以使用VStack来组合每个计划的内容,并使用HStack将它们水平排列。
示例:创建水平堆叠视图
HStack {
VStack {
Text("Basic")
.font(.system(.title, design: .rounded))
.fontWeight(.black)
.foregroundColor(.white)
Text("$9")
.font(.system(size: 40, weight: .heavy, design: .rounded))
.foregroundColor(.white)
Text("per month")
.font(.headline)
.foregroundColor(.white)
}
.padding(40)
.background(Color.purple)
.cornerRadius(10)
VStack {
Text("Pro")
.font(.system(.title, design: .rounded))
.fontWeight(.black)
Text("$19")
.font(.system(size: 40, weight: .heavy, design: .rounded))
Text("per month")
.font(.headline)
.foregroundColor(.gray)
}
.padding(40)
.background(Color(red: 240/255, green: 240/255, blue: 240/255))
.cornerRadius(10)
}
在这个示例中,HStack将两个VStack水平排列,每个VStack代表一个价格计划。
通过调整padding、background和cornerRadius修饰符,我们可以美化每个计划的外观。
设置堆叠间距
为了在Basic和Pro计划之间添加一些间距,可以在HStack中使用spacing参数:
HStack(spacing: 15) {
// Basic 和 Pro 计划的 VStack
}
提取视图以组织代码
随着用户界面的复杂度增加,ContentView中的代码会变得冗长且难以维护。为了提高代码的可读性和可维护性,建议将重复的视图提取为独立的自定义视图。
示例:提取PricingView
-
提取Basic计划的
VStack:- 按住
Control键并点击VStack关键词,选择Refactor > Extract Subview。 - 将生成的
ExtractedView重命名为PricingView。
- 按住
-
修改
PricingView以接受参数:
struct PricingView: View {
var title: String
var price: String
var textColor: Color
var bgColor: Color
var body: some View {
VStack {
Text(title)
.font(.system(.title, design: .rounded))
.fontWeight(.black)
.foregroundColor(textColor)
Text(price)
.font(.system(size: 40, weight: .heavy, design: .rounded))
.foregroundColor(textColor)
Text("per month")
.font(.headline)
.foregroundColor(textColor)
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 100)
.padding(40)
.background(bgColor)
.cornerRadius(10)
}
}
- 使用
PricingView替换原有的VStack:
struct ContentView: View {
var body: some View {
VStack {
HeaderView()
HStack(spacing: 15) {
PricingView(title: "Basic", price: "$9", textColor: .white, bgColor: .purple)
PricingView(title: "Pro", price: "$19", textColor: .black, bgColor: Color(red: 240/255, green: 240/255, blue: 240/255))
}
.padding(.horizontal)
// 其他视图
}
}
}
通过提取视图,我们使ContentView变得更加简洁,并提高了代码的复用性。
使用ZStack
ZStack允许你将一个视图叠加在另一个视图之上,这对于在现有视图上添加标签或装饰非常有用。
在我们的示例中,我们希望在Pro计划块上叠加一个“Best for designer”的标签。
示例:在Pro计划上叠加标签
ZStack {
PricingView(title: "Pro", price: "$19", textColor: .black, bgColor: Color(red:240/255, green: 240/255, blue: 240/255))
Text("Best for designer")
.font(.system(.caption, design: .rounded))
.fontWeight(.bold)
.foregroundColor(.white)
.padding(5)
.background(Color(red: 255/255, green: 183/255, blue: 37/255))
.offset(x: 0, y: 87)
}
在这个示例中,ZStack将PricingView和Text视图叠加在一起。通过调整offset修饰符,可以控制标签的位置。
使用Spacer进行布局调整
在SwiftUI中,Spacer用于在堆栈中创建弹性空间,推动其他视图到堆栈的边缘或特定位置。这类似于在UIKit中使用自动布局约束。
示例:调整HeaderView的对齐方式
struct HeaderView: View {
var body: some View {
HStack {
VStack(alignment: .leading, spacing: 2) {
Text("Choose")
.font(.system(.largeTitle, design: .rounded))
.fontWeight(.black)
Text("Your Plan")
.font(.system(.largeTitle, design: .rounded))
.fontWeight(.black)
}
Spacer()
}
.padding()
}
}
在这个示例中,Spacer将VStack推向左侧,实现左对齐。
示例:在ContentView中添加Spacer
struct ContentView: View {
var body: some View {
VStack {
HeaderView()
HStack(spacing: 15) {
PricingView(title: "Basic", price: "$9", textColor: .white, bgColor: .purple)
PricingView(title: "Pro", price: "$19", textColor: .black, bgColor: Color(red: 240/255, green: 240/255, blue: 240/255))
}
.padding(.horizontal)
ZStack {
PricingView(title: "Team", price: "$299", textColor: .white, bgColor: Color(red: 62/255, green: 63/255, blue: 70/255), icon: "wand.and.rays")
.padding()
Text("Perfect for teams with 20 members")
.font(.system(.caption, design: .rounded))
.fontWeight(.bold)
.foregroundColor(.white)
.padding(5)
.background(Color(red: 255/255, green: 183/255, blue: 37/255))
.offset(x: 0, y: 110)
}
// 添加Spacer推送内容到顶部
Spacer()
}
}
}
通过在VStack末尾添加Spacer,可以将所有内容推向顶部,确保布局在不同屏幕尺寸下的一致性。
练习:创建一个网格布局
现在,你已经熟悉了VStack、HStack和ZStack的基本用法,接下来的练习是创建一个类似于图28所示的网格布局。
使用系统图像(SF Symbols)来装饰每个网格项。
步骤:
-
创建
PricingView的可选图标功能:- 修改
PricingView,添加一个可选的icon参数。
struct PricingView: View {var title: Stringvar price: Stringvar textColor: Colorvar bgColor: Colorvar icon: String?var body: some View {VStack {if let icon = icon {Image(systemName: icon).font(.largeTitle).foregroundColor(textColor)}Text(title).font(.system(.title, design: .rounded)).fontWeight(.black).foregroundColor(textColor)Text(price).font(.system(size: 40, weight: .heavy, design: .rounded)).foregroundColor(textColor)Text("per month").font(.headline).foregroundColor(textColor)}.frame(minWidth: 0, maxWidth: .infinity, minHeight: 100).padding(40).background(bgColor).cornerRadius(10)}} - 修改
-
添加Team计划:
ZStack {PricingView(title: "Team", price: "$299", textColor: .white, bgColor: Color(red: 62/255, green: 63/255, blue: 70/255), icon: "wand.and.rays").padding()Text("Perfect for teams with 20 members").font(.system(.caption, design: .rounded)).fontWeight(.bold).foregroundColor(.white).padding(5).background(Color(red: 255/255, green: 183/255, blue: 37/255)).offset(x: 0, y: 110)} -
创 建网格布局:
- 使用
HStack和VStack的组合,创建一个3x3的网格,每个单元包含一个图标和文本。
struct GridView: View {let items = [("SwiftUI", "Learn SwiftUI", "swift"),("Combine", "Reactive Programming", "arrow.left.arrow.right.circle"),("Xcode", "IDE for Apple", "hammer"),("UIKit", "Legacy Framework", "rectangle.and.pencil.and.ellipsis"),("CoreData", "Data Persistence", "database"),("ARKit", "Augmented Reality", "arkit"),("MapKit", "Maps Integration", "map"),("SpriteKit", "2D Games", "gamecontroller"),("Metal", "High-Performance Graphics", "cpu")]var body: some View {VStack(spacing: 20) {ForEach(0..<3) { row inHStack(spacing: 15) {ForEach(0..<3) { column inlet index = row * 3 + columnif index < items.count {PricingView(title: items[index].0, price: items[index].1, textColor: .white, bgColor: .blue, icon: items[index].2)}}}}}.padding()}} - 使用
-
在
ContentView中展示GridView:struct ContentView: View {var body: some View {VStack {HeaderView()HStack(spacing: 15) {PricingView(title: "Basic", price: "$9", textColor: .white, bgColor: .purple)PricingView(title: "Pro", price: "$19", textColor: .black, bgColor: Color(red: 240/255, green: 240/255, blue: 240/255))}.padding(.horizontal)GridView()Spacer()}}}
通过这个练习,你将学会如何使用堆栈视图组合多个视图,创建出复杂且响应式的用户界面。
总结
本教程详细介绍了SwiftUI中的堆栈布局,包括VStack、HStack和ZStack的使用方法。通过逐步构建和提取视图,你学会了如何组织和优化代码,使其更具可读性和可维护性。此外,通过练习创建网格布局,你掌握了如何结合使用不同类型的堆栈视图,构建出复杂的用户界面。
SwiftUI的堆栈布局简化了用户界面的构建过程,使得布局更直观且易于适应不同的屏幕尺寸。随着你对堆栈视图的深入理解,你将能够创建出更加复杂和专业的应用界面。
继续探索SwiftUI的其他功能,如动画、数据绑定和自定义控件,将进一步提升你的开发技能,帮助你创建更具吸引力和互动性的应用。