ios(swiftui) 属性包装器详解
目录
1. @State
2. @Binding
3. @ObservedObject 和@Published
4. @StateObject
5. @EnvironmentObject和@Environment
6. @AppStorage
在 SwiftUI 中,属性包装器用于增强和管理视图的状态,以及处理视图与数据模型之间的绑定和交互。下面是一些常见的属性包装器:
1. @State
使用@State
属性包装器的变量是私有的,并且仅在创建它们的视图中使用。当用户与界面交互引起状态改变时,SwiftUI会重新绘制依赖这些状态的视图。这是响应式编程思想在SwiftUI中的表现。
这里有一个简单的例子,演示如何使用@State
:
import SwiftUI
struct ContentView: View {
// 使用@State修饰的计数器变量
@State private var counter = 0
var body: some View {
VStack {
Text("你点击了 \(counter) 次")
Button("点击我") {
// 因为counter是@State变量,改变它的值将会重新绘制视图
self.counter += 1
}
}
}
}
在这个例子中,每当counter
变量的值改变时(例如,用户每次点击按钮时),SwiftUI都会重新调用body
属性,因此文本显示的计数器值会更新。
2. @Binding
允许视图共享并能够双向绑定到外部的状态。这常用于将父视图的状态传递给子视图。
@Binding
是一个属性包装器,它允许子视图共享父视图的状态。@Binding
为子视图提供对父视图的某个状态的引用,这样一来,子视图可以读取这个状态并能够在发生更改时更新它,而不是维护它自己的状态副本。
当你使用 @Binding
时,它实际上是对一个 @State
、@ObservedObject
、@EnvironmentObject
或其他具有可观察状态的源的引用。
以下是一个简单的例子说明如何使用 @Binding
:
首先,假设你有一个父视图,使用 @State
管理一个布尔值 isOn
:
struct ParentView: View {
@State private var isOn = false
var body: some View {
VStack {
Toggle("Switch", isOn: $isOn)
ChildView(isOn: $isOn) // 传递 @State 给子视图
}
}
}
接着,你有一个子视图 ChildView
,需要使用从父视图传递的 isOn
状态:
struct ChildView: View {
@Binding var isOn: Bool // 使用 @Binding 接收父视图的状态
var body: some View {
Text(isOn ? "It's on" : "It's off")
}
}
在这个例子中,子视图 ChildView
可以直接显示 isOn
的状态,也能够在不同的视图层级之间共享和修改这个状态,如果 isOn
在子视图中被修改,父视图中的 @State
也会相应更新。
3. @ObservedObject
和@Published
@Published
通常用于 ObservableObject
里的属性。当给标记为 @Published
的属性赋值时,这个改动会发布出去,所有观察者都可以接收到这个改变。
@ObservedObject
和 @Binding
类似,但是用于当数据模型遵守 ObservableObject
协议时。它会使得视图自动更新以响应可观察对象中发生的变化。
声明一个外部来源的可观察对象。当这个可观察对象发生变化时,使用 @ObservedObject
标记的视图会被重新绘制。这使得在SwiftUI中实现数据的双向绑定和状态管理变得更加简单和直观。
这里有一些基础的概念和使用示例:
可观察对象 (Observable Object)
可观察对象通常是遵循了 ObservableObject
协议的类,并且通过 @Published
属性包装器来声明那些当变化时需要通知视图重新渲染的属性。
import Combine
import SwiftUI
class ExampleModel: ObservableObject {
@Published var score = 0
}
使用 @ObservedObject
在SwiftUI视图中使用 @ObservedObject
来观察这些对象的变化:
struct ExampleView: View {
@ObservedObject var model: ExampleModel
var body: some View {
Text("Score: \(model.score)")
.onTapGesture {
model.score += 10
}
}
}
在这个例子中,每次点击文本时,模型的 score
属性会增加10。由于 score
被标记为 @Published
,且 ExampleModel
遵循了 ObservableObject
,所以每次 score
改变时,使用了 @ObservedObject
的 ExampleView
都将重新绘制,反映出新的分数。
注意事项
- 当使用
@ObservedObject
时,你需要确保这个对象在视图更新时不会被销毁或者重新创建,否则会丢失其状态。这通常意味着这个对象是由外部传递给视图的,或者在视图的父级或共享环境(如使用EnvironmentObject
)中保存。 - 对于视图自己的内部状态,你通常会使用
@State
或者@StateObject
作为属性包装器。
@StateObject vs @ObservedObject
不要混淆 @ObservedObject
和 @StateObject
:
@StateObject
用于视图对数据所有权的声明(它负责创建这个对象),并且它会在视图重新绘制时保持对象的生命周期。它在SwiftUI 2.0中引入,用来取代在视图体内部初始化@ObservedObject
的用法。@ObservedObject
通常是从父视图或其他部分的应用程序传递过来的,并且视图不负责管理其生命周期。
在实践中,你需要根据实际的数据所有权和生命周期管理要求来选择使用 @StateObject
还是 @ObservedObject
。
4. @StateObject
- 在 SwiftUI 2.0 中引入,用来初始化可观察对象,并且拥有对该对象的所有权。也就是说它会构建并保持对象直到视图的整个生命周期结束。
@StateObject
与 @ObservedObject
类似,都用于引用遵守 ObservableObject
协议的类实例。这种遵守 ObservableObject
协议的类会发布对其属性的更改,这样 SwiftUI 就可以在这些属性变化时更新 UI。
两者之间的区别在于它们的用途和生命周期:
-
@StateObject
应当用于创建和拥有对应的 observable object,即表示该视图负责初始化这个对象,并且是这个对象的“源头”或首个拥有者。这保证了状态对象不会因为视图的重新渲染而被重新创建。 -
@ObservedObject
应当用于那些已经由其他部分创建的 observable objects。其实际上是对从外部传入的状态对象的引用,意味着它不负责该对象的生命周期。
在以下情形下使用 @StateObject
:
import SwiftUI
class ExampleModel: ObservableObject {
@Published var count: Int = 0
// Other properties and methods
}
struct ExampleView: View {
@StateObject var model = ExampleModel()
var body: some View {
// UI elements that use 'model'
Text("Count: \(model.count)")
.onTapGesture {
model.count += 1
}
}
}
这个例子展示了如何在视图中创建一个 @StateObject
以存储和管理状态。在视图的生命周期中,ExampleModel
对象将保持活动并不会因为视图的重建而被销毁或重置。使用这种方式,你可以确保状态的一致性并避免不必要的对象重建
5. @EnvironmentObject和@Environment
@EnvironmentObject
可以从环境中获取共享的数据模型,这个属性包装器不负责创建对象,而是假定共享的对象已经被其它某部分的代码添加到环境中去了
@Environment
允许视图访问从 iOS 提供的环境值,例如 @Environment(\.presentationMode)
可以访问视图的表现模式。
@EnvironmentObject
和 @Environment
都是用于数据传递和状态共享的属性包装器,不过它们在使用中有一些区别。
(1)@EnvironmentObject
:
@EnvironmentObject
用于向视图的层次结构中传递共享的数据对象。你可以在应用的任意位置注入这个共享的数据对象,让其他需要这些数据的视图可以直接访问它,而不需要通过视图参数来逐层传递。这增加了数据共享的便捷性,尤其是在大型项目中。它通常用于类似于全局状态或者应用中的共享数据模型。
使用@EnvironmentObject
时,首先需要在某个视图之外的地方创建一个可观察对象。该对象需要遵守ObservableObject
协议。然后,在视图层次结构的上游 somewhere(比如在顶级视图或者场景代理中) 你需要将这个对象作为环境对象注入。在需要访问这些数据的视图中,你使用@EnvironmentObject
来声明这个依赖,然后 Swift UI会自动为你提供这个对象。
示例代码:
class SharedData: ObservableObject {
@Published var value: String = "Hello, World!"
}
struct ContentView: View {
// 注入环境对象
@EnvironmentObject var sharedData: SharedData
var body: some View {
Text(sharedData.value)
}
}
let sharedData = SharedData()
let contentView = ContentView().environmentObject(sharedData)
如果尝试访问未注入的@EnvironmentObject
,应用会崩溃。
(2)@Environment
:
@Environment
用于读取从环境中传递下来的值,如系统设置、接口样式、布局方向等。相比@EnvironmentObject
,@Environment
更多用于访问由 SwiftUI 框架维护的预设值,而不是自定义的可观测对象。一个常见的使用场景是,读取系统的颜色方案(.colorScheme
),或者是当前的时间区域(.timeZone
)。
示例代码:
struct ContentView: View {
// 从环境中读取值
@Environment(\.colorScheme) var colorScheme
var body: some View {
Text("The current color scheme is \(colorScheme == .dark ? "Dark" : "Light")")
}
}
在这个例子中,我们没有注入任何自定义对象到环境中。相反,我们直接访问了 SwiftUI 环境中的预设值。
两者虽然看起来类似,但根据使用场景的不同,开发者可以选择最适合的一个。@EnvironmentObject
更适合那些全局共享状态的情景,而@Environment
更适合需要访问由系统维护的环境值。
6. @AppStorage
-在 SwiftUI 2.0 中引入,用于简单的数据持久化,当读写 UserDefaults
时自动同步视图。
@AppStorage
是一个 Swift 属性包装器(property wrapper),提供了一种将用户默认设置或应用设置存储在 UserDefaults
中的便捷方式。使用 @AppStorage
, 你可以创建一个绑定到 UserDefaults
中具体键的属性,当该属性的值发生变化时,UserDefaults
会自动更新,反之亦然。
在 SwiftUI 中,@AppStorage
的使用十分普遍,特别是用来响应某些设置或偏好的变化,并据此更新UI。这种数据持久化的方式适用于存储少量的用户配置信息,例如:标记应用是否为首次启动、用户的暗黑模式偏好、或者任何小型配置数据。
这里是一个基本的使用示例:
import SwiftUI
struct ContentView: View {
// 使用 @AppStorage 监视对应的 UserDefaults 键值对,当值变化时自动更新视图。
@AppStorage("isDarkMode") private var isDarkMode = false
var body: some View {
VStack {
Text(isDarkMode ? "Dark Mode is ON" : "Dark Mode is OFF")
// 切换按钮可以更改 @AppStorage 绑定的值
Button("Toggle Dark Mode") {
isDarkMode.toggle()
}
}
}
}
在上面的例子中,isDarkMode
属性绑定到了 UserDefaults
中的 "isDarkMode"
键。当你点击按钮切换 isDarkMode
的值时,这个值会自动保存到 UserDefaults
并在下次应用启动时保留。同时 UI 也会响应这个值的变化并立即更新。这种简单的数据绑定方法让你能够不必直接操作 UserDefaults
API 而轻松保存和访问用户设置。
官网:SwiftUI | Apple Developer Documentation