一、 View 协议
- 定义:SwiftUI 中所有视图都必须遵循的协议
- 要求:必须实现
body计算属性 - 目的:为 SwiftUI 提供统一的视图接口
@MainActor @preconcurrency public protocol View {
associatedtype Body : View
/// The content and behavior of the view.
///
/// When you implement a custom view, you must implement a computed
/// `body` property to provide the content for your view. Return a view
/// that's composed of built-in views that SwiftUI provides, plus other
/// composite views that you've already defined:
///
/// struct MyView: View {
/// var body: some View {
/// Text("Hello, World!")
/// }
/// }
///
/// For more information about composing views and a view hierarchy,
/// see <doc:Declaring-a-Custom-View>.
@ViewBuilder @MainActor @preconcurrency var body: Self.Body { get }
}
例
struct MyView: View {
var body: some View {
Text("Hello, World!")
}
}
1、 var body: some View
语法组成
var body::计算属性,View 协议要求some View:不透明返回类型(Opaque Return Type)
为什么使用 some View
// 传统写法 - 类型声明复杂
var body: VStack<TupleView<(Text, Button<Text>)>> {
VStack {
Text("Hello")
Button("Click") {}
}
}
// 使用 some View - 简洁
var body: some View {
VStack {
Text("Hello")
Button("Click") {}
}
}
some View 中可以包含的内容
- ✅ 单个视图:
Text("Hello") - ✅ 布局容器:
VStack { ... } - ✅ 条件视图:
if condition { ... } - ✅ 循环视图:
ForEach { ... } - ✅ 修饰器链:
.padding().background()
非常重要的限制
// ❌ 不能直接写多个并列视图
var body: some View {
Text("第一个")
Text("第二个") // 编译错误!
}
// ✅ 必须用容器包装
var body: some View {
VStack {
Text("第一个")
Text("第二个")
}
}
逻辑代码的处理
// ❌ 错误:混合语句破坏隐式返回
var body: some View {
let x = 5
Text("Hello") // 编译错误!
}
// ✅ 方案1:显式返回
var body: some View {
let x = 5
return Text("Hello \(x)")
}
// ✅ 方案2:提取到计算属性
private var greeting: String {
let x = 5
return "Hello \(x)"
}
var body: some View {
Text(greeting)
}
并列视图限制
什么是并列视图
在同一层级直接放置多个视图,没有容器包装。
为什么不能并列
- 单一返回值原则:
body只能返回一个视图 - 类似函数返回值:不能同时返回多个值
解决方案
// 垂直排列
VStack {
Text("第一行")
Text("第二行")
Button("按钮") {}
}
// 水平排列
HStack {
Image("icon")
Text("标题")
Spacer()
}
// 层叠排列
ZStack {
Color.blue
Text("前景文字")
}
二、 容器视图
1、布局容器
VStack(alignment: .leading, spacing: 16) { } // 垂直堆叠
HStack(alignment: .top, spacing: 8) { } // 水平堆叠
ZStack(alignment: .center) { } // 层叠布局
LazyVStack { } // 延迟垂直堆叠
LazyHStack { } // 延迟水平堆叠
2、网格容器
LazyVGrid(columns: columns, spacing: 16) { } // 垂直网格
LazyHGrid(rows: rows, spacing: 16) { } // 水平网格
3、滚动容器
ScrollView(.vertical) { } // 垂直滚动
ScrollView(.horizontal) { } // 水平滚动
List { } // 列表
4、导航容器
NavigationView { } // 导航视图
NavigationStack { } // 新导航栈 (iOS 16+)
TabView { } // 标签页
5、表单容器
Form { } // 表单
Section("标题") { } // 分组
Group { } // 分组(无样式)
常见组合模式
1、经典页面结构
NavigationView {
ScrollView {
VStack(spacing: 20) {
HeaderView()
ContentView()
FooterView()
}
.padding()
}
.navigationTitle("页面标题")
}
2、标签页应用
TabView {
NavigationView {
ScrollView {
VStack {
Text("内容")
}
}
.navigationTitle("标题")
}
.tabItem {
Image(systemName: "house")
Text("首页")
}
}
3、网格布局
ScrollView {
LazyVGrid(columns: columns, spacing: 16) {
ForEach(items) { item in
ItemView(item: item)
}
}
.padding()
}
错误的嵌套示例
// ❌ 错误:导航栏会跟着滚动
ScrollView {
NavigationView {
VStack {
Text("内容")
}
}
}
// ✅ 正确:导航栏固定,内容滚动
NavigationView {
ScrollView {
VStack {
Text("内容")
}
}
}
三、 ForEach详解
ForEach 的本质
- 不是 Swift 语法:是 SwiftUI 框架的结构体
- 遵循 View 协议:用于在声明式 UI 中创建视图
- 与 Swift for 循环不同:用于视图创建,不是逻辑处理
// Swift 原生 for 循环:用于逻辑处理
for item in items {
print(item)
processItem(item)
}
// SwiftUI ForEach:用于创建视图
ForEach(items, id: \.id) { item in
Text(item.name)
}
ForEach 基本使用
// 基本用法
ForEach(items, id: \.id) { item in
Text(item.name)
}
// 数字范围
ForEach(1...10, id: \.self) { number in
Text("第 \(number) 项")
}
// 枚举索引
ForEach(Array(items.enumerated()), id: \.offset) { index, item in
Text("\(index): \(item.name)")
}
四、视图修饰
通用修饰器(大部分 View 都支持)
Text("Hello")
.padding() // 内边距
.background(Color.blue) // 背景色
.foregroundColor(.white) // 前景色
.cornerRadius(8) // 圆角
.shadow(radius: 2) // 阴影
.opacity(0.8) // 透明度
.frame(width: 200, height: 50) // 尺寸
.hidden() // 隐藏
.disabled(false) // 禁用状态
特定 View 的专用修饰器
Text
Text("Hello")
.font(.title) // 字体大小
.fontWeight(.bold) // 字体粗细
.multilineTextAlignment(.center) // 多行文本对齐
.lineLimit(3) // 行数限制
.lineSpacing(5) // 行间距
.truncationMode(.tail) // 截断模式
Button
Button("Click") {}
.buttonStyle(.borderedProminent) // 按钮样式
.controlSize(.large) // 控件大小
.keyboardShortcut(.space) // 键盘快捷键
Image
Image("logo")
.resizable() // 可调整大小
.scaledToFit() // 缩放模式
.aspectRatio(contentMode: .fill) // 宽高比
.clipShape(Circle()) // 裁剪形状
TextField
TextField("Enter text", text: $text)
.textFieldStyle(.roundedBorder) // 输入框样式
.keyboardType(.emailAddress) // 键盘类型
.autocapitalization(.none) // 自动大写
.disableAutocorrection(true) // 禁用自动纠正
List
List {
Text("Item 1")
}
.listStyle(.plain) // 列表样式
.listRowSeparator(.hidden) // 行分割线
.listRowBackground(Color.gray) // 行背景
行高设置不同的方式
// 文本行高
Text("多行文本")
.lineSpacing(10) // 行间距
// 列表行高
List {
Text("Item")
.frame(height: 60) // 设置行高
}
// VStack 间距
VStack(spacing: 20) { // 元素间距
Text("第一行")
Text("第二行")
}