Skip to main content

SwiftUI 布局指南:使用 VStack、HStack 和 ZStack 构建复杂用户界面

鱼雪

在你已经了解了SwiftUI的基础知识并掌握了如何显示文本内容后,现在是时候学习如何布局用户界面了。

上一篇 SwiftUI 图片视图 介绍了 SwiftUI 中使用 Image 显示图片。

今天我们介绍 SwiftUI 中使用 VStackHStackZStack 进行布局。

在本教程中,我们将深入探讨SwiftUI中的堆栈布局,包括VStackHStackZStack, 并展示如何通过这些堆栈构建复杂的网格布局。通过逐步构建一个简单的网格界面,你将学会如何有效地组合视图, 创建出理想的用户界面。

目录

  1. 什么是SwiftUI中的堆栈
  2. 理解VStack、HStack和ZStack
  3. 创建一个新的SwiftUI项目
  4. 使用VStack
  5. 使用HStack
  6. 提取视图以组织代码
  7. 使用ZStack
  8. 使用Spacer进行布局调整
  9. 练习:创建一个网格布局
  10. 总结

什么是SwiftUI中的堆栈

堆栈(Stacks)是SwiftUI中用于组合视图的核心组件。通过在水平(HStack)和垂直(VStack)堆栈中组合视图, 你可以构建出复杂且响应式的用户界面。在UIKit中,构建适应不同屏幕尺寸的界面通常需要使用自动布局(Auto Layout), 这对于初学者来说可能较为复杂。而在SwiftUI中,堆栈简化了这一过程,使得布局更加直观和易于掌握。

理解VStack、HStack和ZStack

SwiftUI提供了三种主要的堆栈类型,帮助开发者以不同的方向组合视图:

  • VStack:垂直堆叠视图,将子视图按垂直方向排列。
  • HStack:水平堆叠视图,将子视图按水平方向排列。
  • ZStack:叠加堆叠视图,将子视图层层叠加在一起。

下图展示了这三种堆栈如何组织视图:

Stacks in SwiftUI

创建一个新的SwiftUI项目

首先,启动Xcode并创建一个新的项目:

  1. 打开Xcode,选择创建一个新的Xcode项目
  2. 在iOS类别下选择App模板,然后点击下一步
  3. 输入项目名称,例如SwiftUIStacks。组织名称可以填写你的公司或组织名称,如com.yourcompany
  4. 用户界面(User Interface)选项中选择SwiftUI,语言选择Swift
  5. 点击下一步,选择一个文件夹来保存项目。

创建项目后,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中的视图是居中的。要将它们左对齐,并调整视图之间的间距,可以使用alignmentspacing参数:

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代表一个价格计划。 通过调整paddingbackgroundcornerRadius修饰符,我们可以美化每个计划的外观。

设置堆叠间距

为了在Basic和Pro计划之间添加一些间距,可以在HStack中使用spacing参数:

HStack(spacing: 15) {
// Basic 和 Pro 计划的 VStack
}

提取视图以组织代码

随着用户界面的复杂度增加,ContentView中的代码会变得冗长且难以维护。为了提高代码的可读性和可维护性,建议将重复的视图提取为独立的自定义视图。

示例:提取PricingView

  1. 提取Basic计划的VStack

    • 按住Control键并点击VStack关键词,选择Refactor > Extract Subview
    • 将生成的ExtractedView重命名为PricingView
  2. 修改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)
}
}
  1. 使用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)
}

在这个示例中,ZStackPricingViewText视图叠加在一起。通过调整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()
}
}

在这个示例中,SpacerVStack推向左侧,实现左对齐。

示例:在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,可以将所有内容推向顶部,确保布局在不同屏幕尺寸下的一致性。

练习:创建一个网格布局

现在,你已经熟悉了VStackHStackZStack的基本用法,接下来的练习是创建一个类似于图28所示的网格布局。 使用系统图像(SF Symbols)来装饰每个网格项。

步骤:

  1. 创建PricingView的可选图标功能

    • 修改PricingView,添加一个可选的icon参数。
    struct PricingView: View {
    var title: String
    var price: String
    var textColor: Color
    var bgColor: Color
    var 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)
    }
    }
  2. 添加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)
    }
  3. 创建网格布局

    • 使用HStackVStack的组合,创建一个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 in
    HStack(spacing: 15) {
    ForEach(0..<3) { column in
    let index = row * 3 + column
    if index < items.count {
    PricingView(title: items[index].0, price: items[index].1, textColor: .white, bgColor: .blue, icon: items[index].2)
    }
    }
    }
    }
    }
    .padding()
    }
    }
  4. 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中的堆栈布局,包括VStackHStackZStack的使用方法。通过逐步构建和提取视图,你学会了如何组织和优化代码,使其更具可读性和可维护性。此外,通过练习创建网格布局,你掌握了如何结合使用不同类型的堆栈视图,构建出复杂的用户界面。

SwiftUI的堆栈布局简化了用户界面的构建过程,使得布局更直观且易于适应不同的屏幕尺寸。随着你对堆栈视图的深入理解,你将能够创建出更加复杂和专业的应用界面。

继续探索SwiftUI的其他功能,如动画、数据绑定和自定义控件,将进一步提升你的开发技能,帮助你创建更具吸引力和互动性的应用。