ToolkitX
知识库工具箱

SwiftUI 入门

声明式 UI、数据流、动画

25min·进阶

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 是什么类型?