本篇目标
理解 SwiftUI 数据驱动的核心理念,掌握 @State、@Binding 的用法,学会用状态驱动界面刷新,能构建带交互的界面。
一、数据驱动的核心理念
SwiftUI 是数据驱动的界面框架:状态变化,界面自动刷新。
核心公式:
UI = f(State)
你只负责描述状态。
SwiftUI 负责根据状态渲染界面。
状态改变时,相关视图自动重建。
对比 UIKit:
方式 | 做法 |
|---|---|
UIKit | 手动更新 UI 元素 |
SwiftUI | 修改状态,界面自动刷新 |
二、@State
@State 用于管理视图内部的状态。
struct CounterView: View {
@State private var count = 0
var body: some View {
VStack {
Text("计数:\(count)")
Button("加一") {
count += 1
}
}
}
}特点:
private修饰,只在本视图内使用。值改变时,视图自动刷新。
适合管理简单、局部的状态。
常用场景:
开关状态
输入框文字
计数器
页面切换标记
struct ToggleView: View {
@State private var isOn = false
var body: some View {
Toggle("启用", isOn: $isOn)
}
}三、@Binding
@Binding 用于在子视图中读写父视图的状态。
父视图:
struct ParentView: View {
@State private var name = ""
var body: some View {
VStack {
Text("你好,\(name)")
ChildView(name: $name)
}
}
}子视图:
struct ChildView: View {
@Binding var name: String
var body: some View {
TextField("输入名字", text: $name)
}
}说明:
$name把 @State 转换成 Binding。子视图通过 @Binding 修改父视图的状态。
适用于父子视图共享状态。
可复用组件:
struct CustomToggle: View {
@Binding var isOn: Bool
let title: String
var body: some View {
HStack {
Text(title)
Spacer()
Toggle("", isOn: $isOn)
.labelsHidden()
}
.padding()
}
}四、@StateObject 与 @ObservedObject
当状态是一个对象,需要用 ObservableObject。
定义 ViewModel:
import SwiftUI
import Combine
class CounterViewModel: ObservableObject {
@Published var count = 0
func increment() {
count += 1
}
}@StateObject(创建):
struct CounterView: View {
@StateObject private var viewModel = CounterViewModel()
var body: some View {
VStack {
Text("计数:\(viewModel.count)")
Button("加一") {
viewModel.increment()
}
}
}
}@ObservedObject(传入):
struct CounterDisplay: View {
@ObservedObject var viewModel: CounterViewModel
var body: some View {
Text("计数:\(viewModel.count)")
}
}区别:
属性包装器 | 用途 | 生命周期 |
|---|---|---|
@StateObject | 视图自己创建并持有 | 跟随视图 |
@ObservedObject | 外部传入 | 由外部管理 |
使用原则:
视图自己创建的对象:用 @StateObject。
视图外部传入的对象:用 @ObservedObject。
五、@EnvironmentObject
@EnvironmentObject 用于跨视图共享状态。
注入:
class AppState: ObservableObject {
@Published var username = "Guest"
}
@main
struct MyApp: App {
@StateObject private var appState = AppState()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(appState)
}
}
}使用:
struct ProfileView: View {
@EnvironmentObject var appState: AppState
var body: some View {
Text("用户:\(appState.username)")
}
}特点:
无需逐层传递。
通过环境注入,任意子视图都能访问。
适合全局状态:用户信息、主题、设置。
六、@Environment
@Environment 用于读取系统环境值。
struct MyView: View {
@Environment(\.colorScheme) var colorScheme
@Environment(\.dismiss) var dismiss
var body: some View {
Text(colorScheme == .dark ? "深色模式" : "浅色模式")
Button("关闭") {
dismiss()
}
}
}常用环境值:
环境值 | 说明 |
|---|---|
| 深色/浅色模式 |
| 关闭当前视图 |
| 水平尺寸类 |
| 语言区域 |
| 日历 |
| 应用生命周期 |
七、@Published
@Published 用于在 ObservableObject 中标记可发布属性。
class LoginViewModel: ObservableObject {
@Published var username = ""
@Published var password = ""
@Published var isLoggedIn = false
var canSubmit: Bool {
!username.isEmpty && !password.isEmpty
}
func login() {
isLoggedIn = true
}
}特点:
值变化时,通知订阅者。
只能在 ObservableObject 中使用。
配合 @StateObject / @ObservedObject 使用。
八、状态提升与单向数据流
状态提升:把状态放到共同的父视图,通过 @Binding 传递。
struct ParentView: View {
@State private var text = ""
var body: some View {
VStack {
TextInputView(text: $text)
TextPreviewView(text: text)
}
}
}
struct TextInputView: View {
@Binding var text: String
var body: some View {
TextField("输入", text: $text)
}
}
struct TextPreviewView: View {
let text: String
var body: some View {
Text("预览:\(text)")
}
}单向数据流:
状态定义在顶层。
数据向下传递。
事件向上传递。
保证数据一致性和可预测性。
九、综合示例:登录表单
struct LoginView: View {
@State private var username = ""
@State private var password = ""
@State private var isLoggedIn = false
@State private var showError = false
var body: some View {
VStack(spacing: 16) {
TextField("用户名", text: $username)
.textFieldStyle(.roundedBorder)
SecureField("密码", text: $password)
.textFieldStyle(.roundedBorder)
Button("登录") {
if username.isEmpty || password.isEmpty {
showError = true
} else {
isLoggedIn = true
}
}
.buttonStyle(.borderedProminent)
.disabled(username.isEmpty || password.isEmpty)
if showError {
Text("请填写完整信息")
.foregroundColor(.red)
.font(.caption)
}
if isLoggedIn {
Text("登录成功:\(username)")
.foregroundColor(.green)
}
}
.padding()
}
}十、避坑指南
@State 用 private:避免外部修改。
不要用 @State 存对象:对象用 @StateObject。
@ObservedObject 不持有生命周期:外部传入的对象用 @ObservedObject。
@EnvironmentObject 未注入会崩溃:确保上层注入。
@Published 只能在类中:结构体用 @State。
避免状态过多:状态太多说明视图拆分不清晰。
状态尽量靠近使用位置:就近原则,避免全局污染。
@Published 需要 import Combine:@Published 定义在 Combine 框架里,而 import SwiftUI 并不自动带上它。新版编译器(成员导入可见性)下会报「类型不遵循 ObservableObject」,看起来像状态写错了,其实只是少了一行 import Combine。
十一、检查清单
[ ] 理解数据驱动界面的理念
[ ] 会使用 @State 管理局部状态
[ ] 会使用 @Binding 实现父子共享
[ ] 理解 @StateObject 与 @ObservedObject 的区别
[ ] 会使用 @EnvironmentObject 共享全局状态
[ ] 会使用 @Environment 读取系统值
[ ] 能构建带交互的表单界面
[ ] 理解单向数据流
📦 这几种状态管理写法在 HelloWorld 的 SettingsView.swift 里都能看到,文件顶部标了对应的笔记编号。