01. 声明式 UI 和 View
SwiftUI 是声明式 UI 框架,你只需要描述 UI 长什么样,框架自动处理何时更新。View 是协议,所有视图组件都遵循它。body 是计算属性返回一个 View。每次数据变化时 body 自动重新计算,框架会高效地只更新变化的部分(diff 算法)。
swift
import SwiftUI
struct ContentView: View {
@State private var name = ""
var body: some View {
VStack(spacing: 20) {
Text("你好, \(name.isEmpty ? "世界" : name)!")
.font(.largeTitle)
TextField("输入你的名字", text: $name)
.textFieldStyle(.roundedBorder)
.padding()
Button("清空") {
name = ""
}
.buttonStyle(.bordered)
}
.padding()
}
}SwiftUI 的 View 是值类型(struct 而不是 class),每次刷新都重新创建但开销很小。
02. @State、@Binding、@ObservedObject
这几个属性包装器是 SwiftUI 数据流的核心。@State 用于视图内部的简单状态。@Binding 打通父子视图的数据双向绑定,用 $ 前缀获取。@StateObject 创建并持有引用类型的 ObservableObject。@ObservedObject 观察但不持有对象。@EnvironmentObject 从环境中共享数据。
swift
import SwiftUI
// 数据模型
class UserSettings: ObservableObject {
@Published var isDarkMode = false
@Published var fontSize = 14.0
}
// 子视图 - 双向绑定
struct ToggleView: View {
@Binding var isOn: Bool
var body: some View {
Toggle("开关", isOn: $isOn)
}
}
// 主视图
struct SettingsView: View {
@StateObject private var settings = UserSettings()
@State private var showDetail = false
var body: some View {
VStack {
ToggleView(isOn: $showDetail)
if showDetail {
Text("深色模式: \(settings.isDarkMode ? "开" : "关")")
}
}
}
}@StateObject 和 @ObservedObject 容易搞混:持有者用 @StateObject,使用者用 @ObservedObject。
03. 常用视图组合
VStack 垂直排列,HStack 水平排列,ZStack 叠加排列。List 滚动列表(类似 UITableView),ForEach 遍历数据生成视图。NavigationStack 提供导航栏和页面跳转,NavigationLink 是跳转按钮。Spacer 填充剩余空间。这些组件组合在一起就能搭出复杂界面。
swift
struct TaskListView: View {
@State private var tasks = ["买菜", "写代码", "运动"]
@State private var newTask = ""
var body: some View {
NavigationStack {
List {
ForEach(tasks, id: \.self) { task in
NavigationLink(destination: TaskDetailView(task: task)) {
HStack {
Image(systemName: "circle")
Text(task)
}
}
}
.onDelete { indexSet in
tasks.remove(atOffsets: indexSet)
}
}
.navigationTitle("任务列表")
.toolbar {
ToolbarItem {
Button("添加") {
tasks.append(newTask)
newTask = ""
}
}
}
}
}
}ForEach 的 id 参数很重要,SwiftUI 用这个 id 来 diff 视图,必须唯一且稳定。
04. 修饰器链
SwiftUI 用修饰器(modifiers)来配置视图的外观和行为。每个修饰器返回一个新视图,形成链式调用。.font、.foregroundColor 设置文字样式,.padding、.frame 控制布局,.background、.cornerRadius 装饰外观,.onTapGesture、.onAppear 添加交互。
swift
struct CardView: View {
let title: String
let subtitle: String
var body: some View {
VStack(alignment: .leading, spacing: 8) {
Text(title)
.font(.headline)
.foregroundColor(.primary)
Text(subtitle)
.font(.subheadline)
.foregroundColor(.secondary)
}
.padding()
.frame(maxWidth: .infinity, alignment: .leading)
.background(Color.white)
.cornerRadius(12)
.shadow(color: .black.opacity(0.1), radius: 5)
.onTapGesture {
print("点击了卡片: \(title)")
}
}
}修饰器顺序很重要!比如先 .frame 再 .background,背景色只在 frame 范围内。
05. 动画和过渡
SwiftUI 的动画非常简单。.animation 修饰器让任何值变化都有动画过渡。withAnimation {} 包裹状态修改,所有依赖于该状态的变化都会动起来。.transition 定义视图出现消失的方式,配合 .animation 使用。无论隐式还是显式动画,加一个修饰器的事。
swift
struct AnimatedView: View {
@State private var isExpanded = false
@State private var rotation = 0.0
var body: some View {
VStack(spacing: 30) {
// 隐式动画
Circle()
.fill(isExpanded ? .blue : .red)
.frame(width: isExpanded ? 200 : 100, height: isExpanded ? 200 : 100)
.animation(.spring(response: 0.5, dampingFraction: 0.6), value: isExpanded)
// 显式动画
Button("切换 / 旋转") {
withAnimation(.easeInOut(duration: 0.5)) {
isExpanded.toggle()
rotation += 180
}
}
// 过渡动画
if isExpanded {
Text("展开了!")
.transition(.opacity.combined(with: .slide))
}
}
}
}iOS 17 后的自定义动画可以用 KeyframeAnimator 或 PhaseAnimator 实现更复杂的多阶段动画。
知识测验
第 1/4 题正确 0
SwiftUI 中 View 是什么类型?