文章目录
- 引言
- 一、`@State`
- 1.1 基本概念
- 1.2 初始化与默认值
- 1.3 注意事项
- 二、`@Binding`
- 2.1 基本概念
- 2.2 初始化与使用
- 2.3 注意事项
- 三、`@ObservedObject`
- 3.1 基本概念
- 3.2 初始化与使用
- 3.3 注意事项
- 四、`@EnvironmentObject`
- 4.1 基本概念
- 4.2 初始化与使用
- 4.3 注意事项
- 五、`@StateObject`
- 5.1 基本概念
- 5.2 初始化与使用
- 5.3 注意事项
- 六、@ObservedObject、@StateObject、@EnvironmentObject区别及使用场景
- 6.1 区别
- 6.1.1 对象创建和所有权
- 6.1.2 生命周期管理
- 6.1.3 数据传递方式
- 6.2 使用场景
- 6.2.1 `@ObservedObject`
- 6.2.2 `@StateObject`
- 6.2.3 `@EnvironmentObject`
- 七、综合案例
- 7.1 电商购物案例
- 7.2 代码解释
- 7.2.1 数据模型
- 7.2.2 购物车视图模型(`ShoppingCartViewModel`)
- 7.2.3 商品单元格视图(`ProductCell`)
- 7.2.4 商品列表视图(`ProductListView`)
- 7.2.5 购物车视图(`CartView`)
- 7.2.6 主视图(`MainView`)
- 八、小结
引言
在 SwiftUI 中,状态管理是构建交互式和动态用户界面的核心。状态代表着应用程序的数据,当这些数据发生变化时,SwiftUI 会自动更新与之关联的视图,以反映最新的状态。本文将详细介绍 SwiftUI 中几种常见的状态管理方式,包括 @State
、@Binding
、@ObservedObject
、@EnvironmentObject
和 @StateObject
,并探讨它们的使用场景、初始化、默认值设置以及注意事项。
一、@State
1.1 基本概念
@State
是 SwiftUI 中用于管理视图私有状态的属性包装器。它通常用于存储简单的值,如布尔值、整数、字符串等,并且只能在结构体视图中使用。当 @State
变量的值发生变化时,SwiftUI 会重新计算并更新依赖于该变量的视图部分。
1.2 初始化与默认值
@State
变量必须在声明时进行初始化,因为它代表着视图的初始状态。可以为其提供一个默认值,这个默认值将作为视图首次显示时的状态。
import SwiftUIstruct StateExampleView: View {// 初始化 @State 变量并设置默认值@State private var isFavorite = falsevar body: some View {Button(action: {self.isFavorite.toggle()}) {Text(isFavorite ? "已收藏" : "收藏")}}
}
在上述代码中,isFavorite
是一个 @State
变量,初始值为 false
。当按钮被点击时,isFavorite
的值会取反,视图会相应地更新显示内容。
1.3 注意事项
- 私有性:
@State
变量应该是私有的,因为它是视图的内部状态,不应该被外部视图直接访问或修改。 - 值类型:
@State
通常用于存储值类型(如结构体、枚举),因为值类型的赋值会创建一个新的副本,这有助于 SwiftUI 检测状态的变化。 - 视图重建:当
@State
变量的值发生变化时,SwiftUI 会重新计算整个视图的body
属性,因此应避免在body
中执行昂贵的操作。
二、@Binding
2.1 基本概念
@Binding
用于在不同视图之间共享状态,实现双向数据绑定。它允许一个视图修改另一个视图的状态,通常用于将父视图的 @State
变量传递给子视图。
2.2 初始化与使用
@Binding
变量不能直接初始化,它必须通过外部传递的 Binding
实例进行赋值。通常在父视图中使用 $
符号将 @State
变量转换为 Binding
实例,并传递给子视图。
import SwiftUI// 子视图
struct TextFieldView: View {@Binding var text: Stringvar body: some View {TextField("输入文本", text: $text)}
}// 父视图
struct BindingExampleView: View {@State private var inputText = ""var body: some View {VStack {// 将 @State 变量转换为 Binding 并传递给子视图TextFieldView(text: $inputText)Text("你输入的文本是: \(inputText)")}}
}
在上述代码中,TextFieldView
接收一个 @Binding
变量 text
,并将其绑定到 TextField
上。父视图 BindingExampleView
将自己的 @State
变量 inputText
通过 $
符号转换为 Binding
实例传递给子视图。当用户在 TextField
中输入文本时,父视图中的 inputText
会相应更新。
2.3 注意事项
- 依赖外部状态:
@Binding
变量依赖于外部传递的Binding
实例,因此必须确保在使用之前已经正确初始化。 - 数据一致性:由于
@Binding
实现了双向数据绑定,任何对@Binding
变量的修改都会反映到原始的@State
变量上,需要注意数据的一致性和正确性。
三、@ObservedObject
3.1 基本概念
@ObservedObject
用于观察符合 ObservableObject
协议的对象。当被观察对象的 @Published
属性发生变化时,SwiftUI 会自动更新关联的视图。@ObservedObject
通常用于管理复杂的状态逻辑,将状态和业务逻辑封装在一个独立的对象中。
3.2 初始化与使用
@ObservedObject
变量可以在视图中直接初始化,也可以通过外部传递。被观察的对象必须符合 ObservableObject
协议,并且需要使用 @Published
标记需要观察的属性。
import SwiftUI
import Combine// 定义一个符合 ObservableObject 协议的类
class CounterViewModel: ObservableObject {// 使用 @Published 标记需要观察的属性@Published var count = 0func increment() {count += 1}
}struct ObservedObjectExampleView: View {// 初始化 @ObservedObject 变量@ObservedObject private var viewModel = CounterViewModel()var body: some View {VStack {Text("计数: \(viewModel.count)")Button(action: {self.viewModel.increment()}) {Text("增加计数")}}}
}
在上述代码中,CounterViewModel
是一个符合 ObservableObject
协议的类,包含一个 @Published
属性 count
。ObservedObjectExampleView
使用 @ObservedObject
观察 CounterViewModel
的实例。当点击按钮调用 viewModel.increment()
方法时,count
属性的值会改变,SwiftUI 会自动更新 Text
视图以显示新的计数。
3.3 注意事项
- 对象生命周期:
@ObservedObject
不会管理被观察对象的生命周期,因此需要确保对象在视图使用期间不会被销毁。通常在父视图中创建对象并传递给子视图,或者使用@StateObject
来管理对象的生命周期。 - 线程安全:
@Published
属性的修改应该在主线程上进行,因为 SwiftUI 的视图更新是在主线程上执行的。如果在后台线程中修改@Published
属性,可能会导致视图更新不一致或崩溃。
四、@EnvironmentObject
4.1 基本概念
@EnvironmentObject
用于在整个视图层次结构中共享一个 ObservableObject
实例。与 @ObservedObject
不同的是,@EnvironmentObject
可以在多个视图中轻松访问同一个状态对象,而不需要通过层层传递参数。
4.2 初始化与使用
@EnvironmentObject
变量不需要在视图中初始化,它会从视图环境中获取共享的 ObservableObject
实例。在父视图中,需要使用 environmentObject
修饰符将 ObservableObject
实例注入到视图环境中。
import SwiftUI
import Combine