SwiftUI 为我们的应用程序提供了 17
个属性包装器,每个属性包装器都提供不同的功能。知道使用哪个以及何时使用对正确处理至关重要,因此在本文中,我将向您介绍其中的每一个,并为您提供明确的使用指南。
参考链接:https://www.cocoaz.com/swiftui/example-appendix-a-demo2
@AppStorage
从 UserDefaults
读取和写入值。这拥有其数据。更多信息。@Binding
引用另一个视图拥有的值类型数据。在本地更改绑定也会更改远程数据。这不拥有其数据。更多信息。@Environment
允许我们从系统读取数据,例如配色方案,可访问性选项和特征集,但是如果需要,您可以在此处添加自己的密钥。这不拥有其数据。更多信息。@EnvironmentObject
读取我们放入环境中的共享对象。这不拥有其数据。更多信息。@FetchRequest
为特定实体启动核心数据获取请求。这拥有其数据。更多信息。@FocusedBinding
旨在监视键窗口中的值,例如当前选定的文本字段。这不拥有其数据。@FocusedValue
是 @FocusedBinding
的简单版本,不会为您解开绑定值。这不拥有其数据。@GestureState
存储与当前正在进行的手势关联的值,例如您滑动了多远,除非在手势停止时它将重置为其默认值。这拥有其数据。更多信息。@Namespace
创建一个动画名称空间,以允许匹配的几何效果,该效果可以由其他视图共享。这拥有其数据。@NSApplicationDelegateAdaptor
用于创建类并将其注册为 macOS
应用程序的应用程序委托。这拥有其数据。@ObservedObject
引用符合 ObservableObject
协议的外部类的实例。这不拥有其数据。更多信息。@Published
附加到 ObservableObject
内的属性,并告诉 SwiftUI 它应该在更改此属性时刷新使用此属性的所有视图。这拥有其数据。更多信息。@ScaledMetric
会读取用户的动态类型设置,并根据您提供的原始值向上或向下缩放数字。这拥有其数据。更多信息。@SceneStorage
使我们可以保存和还原少量数据以进行状态还原。这拥有其数据。更多信息。@State
允许我们在视图本地操作少量的值类型数据。这拥有其数据。更多信息。@StateObject
用于存储符合 ObservableObject
协议的引用类型数据的新实例。这拥有其数据。更多信息。@UIApplicationDelegateAdaptor
用于创建一个类并将其注册为iOS应用程序的应用程序委托。这拥有其数据。更多信息。作用:@Binding用于在视图之间传递和共享可读写的值。它创建了一个对属性的引用,以便多个视图可以共享同一份数据,并且对数据的更改会在所有引用的地方生效。它做的事情是将值语义的属性“转换”为引用语义。对被声明为 @Binding 的属性进行赋值,改变的将不是属性本身,而是它的引用,这个改变将被向外传递,使用@state可以实现在当前view视图内的状态管理,但是如果需要将状态传递到子视图,并且实现双向绑定就需要使用@Binding来实现
示例:在下面的示例中,父视图通过 @Binding 将布尔值传递给子视图,以控制子视图的可见性。
struct ParentView: View {
@State private var isChildViewVisible = false
var body: some View {
VStack {
Toggle(isOn: $isChildViewVisible) {
Text("Show Child View")
}
if isChildViewVisible {
ChildView(isVisible: $isChildViewVisible)
}
}
}
}
struct ChildView: View {
@Binding var isVisible: Bool
var body: some View {
Text("Child View")
Button(action: {
isVisible = false
}) {
Text("Hide")
}
}
}
作用:使用 @Environment 属性包装器,你可以声明一个属性来获取特定环境变量的值,而无需手动传递它们。当环境变量的值发生变化时,相关的视图会自动更新。
示例:下面是一个使用 @Environment 属性包装器的示例代码:我们使用 @Environment(.colorScheme) 将 colorScheme属性声明为从环境中获取当前的颜色方案。根据颜色方案的值,我们设置不同的文本颜色和背景色。你可以尝试切换模拟器的外观(Light 或 Dark),以查看文本颜色和背景色是如何自动根据环境变量的值而更新的。
import SwiftUI
struct ContentView: View {
@Environment(\.colorScheme) var colorScheme
var body: some View {
if colorScheme == .dark {
Text("Dark Mode")
.foregroundColor(.white)
.background(Color.black)
} else {
Text("Light Mode")
.foregroundColor(.black)
.background(Color.white)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.previewLayout(.sizeThatFits)
}
}
作用:@State 用于在视图中声明可变的状态属性,并自动更新视图。当使用 @State 标记属性时,在 SwiftUI 内部会被自动转换为一对 setter 和 getter,对这个属性进行赋值的操作将会触发 View 的刷新,它的 body 会被再次调用,底层渲染引擎会找出界面上被改变的部分,根据新的属性值计算出新的 View,并进行刷新。
示例:在下面的示例中,我们使用 @State 来声明一个可变的计数属性,点击按钮时,计数会增加并更新视图。
struct ContentView: View {
@State private var count: Int = 0
var body: some View {
Button(action: {
count += 1
}) {
Text("Count: \(count)")
}
}
}
作用:@GestureState 用于跟踪手势的状态和进度。它用于创建自定义手势,并跟踪手势过程中的状态变化。
示例:在下面的示例中,我们使用 @GestureState 跟踪长按手势的状态,并根据手势的状态来改变视图的颜色。
struct ContentView: View {
@GestureState private var isLongPressed = false
var body: some View {
Circle()
.fill(isLongPressed ? Color.red : Color.blue)
.frame(width: 100, height: 100)
.gesture(
LongPressGesture()
.updating($isLongPressed) { currentState, gestureState, _ in
gestureState = currentState
}
)
}
}
作用:@ObservedObject用于观察引用类型对象的变化,并在对象更改时更新视图。它用于管理外部对象的状态,并在该对象发生变化时自动刷新视图。如果说 @State 是全自动驾驶的话,ObservableObject 就是半自动,它需要一些额外的声明。ObservableObject 协议要求实现类型是 class,它只有一个需要实现的属性:objectWillChange。在数据将要发生改变时,这个属性用来向外进行“广播”,它的订阅者 (一般是 View 相关的逻辑) 在收到通知后,对 View 进行刷新。
创建 ObservableObject 后,实际在 View 里使用时,我们需要将它声明为 @ObservedObject。这也是一个属性包装,它负责通过订阅 objectWillChange 这个“广播”,将具体管理数据的 ObservableObject 和当前的 View 关联起来。如果属性定义时加了@Published包装器,就可以省略objectWillChange,系统已经自动帮你实现了objectWillChange,@ObservedObject修饰的必须是遵守ObservableObject 协议的class对象,
class对象的属性只有被@Published修饰时,属性的值修改时,才能被监听到
示例:在下面的示例中,我们创建一个 UserData 类,使用 @ObservedObject标记属性,在视图中观察和使用该对象的属性。
class UserData: ObservableObject {
@Published var name: String = ""
}
struct ContentView: View {
@ObservedObject var user = UserData()
var body: some View {
TextField("Enter your name", text: $user.name)
.textFieldStyle(.roundedBorder)
Text("Hello, \(user.name)!")
}
}
作用:@StateObject 用于在视图中创建一个持久化的可观察对象,并在视图的生命周期内保持持久性。它类似于@ObservedObject,但在对象生命周期中只会创建一次。
示例:在下面的示例中,我们创建一个 DataModel 类,并使用 @StateObject 在视图中创建和使用该对象。
class DataModel: ObservableObject {
@Published var data: [String] = []
}
struct ContentView: View {
@StateObject var dataModel = DataModel()
var body: some View {
VStack {
Button(action: {
dataModel.data.append("New Item")
}) {
Text("Add Item")
}
ForEach(dataModel.data, id: \.self) { item in
Text(item)
}
}
}
}
作用:@EnvironmentObject用于在整个应用程序中共享全局环境对象。它允许在应用程序中的任何地方访问和使用该对象,而无需手动传递数据。在 SwiftUI 中,View 提供了 environmentObject(自定义的Object) 方法,来把某个 ObservableObject 的值注入到当前 View 层级及其子层级中去。在这个 View 的子层级中,可以使用 @EnvironmentObject 来直接获取这个绑定的环境值。@EnvironmentObject 修饰器是针对全局环境的。通过它,我们可以避免在初始 View 时创建 ObservableObject, 而是从环境中获取 ObservableObject
示例:在下面的示例中,我们创建一个 Person类,并使用 @EnvironmentObject 在视图中共享该对象。可以看出我们获取 p这个 ObservableObject 是通过 @EnvironmentObject 修饰器,但是在入口需要传入 .environmentObject(p) 。@EnvironmentObject 的工作方式是在 Environment 查找 Person 实例。
final class Person:ObservableObject{
@Published var name = "我是个人"
}
struct ContentView: View {
var body: some View {
VStack{
let p = Person()
MapView().environmentObject(p)
}
}
}
struct MapView: View {
@EnvironmentObject var p:Person
var body: some View {
VStack{
Text(p.name)
Button("点我") { //添加一个按钮,指定标题文字为 First button
p.name = "1234567890"
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
@Environment 属性包装器只能获取已经存在于环境中的环境变量的值
@EnvironmentObject 属性包装器结合 ObservableObject 协议来创建和传递自定义的环境对象
@SateObject针对引用类型设计,被View持有,当View更新时,实例不会被销毁,与State类似,使得View本身拥有数据,这使得 @StateObject 适用于那些需要持久状态且与视图密切相关的数据对象,比如页面导航、用户输入等。@StateObject
告诉SwiftUI,当这个视图更新时,你希望保留这个对象的一个示例
@ObservedObject只是作为View的数据依赖,不被View持有,View更新时ObservedObject对象可能会被销毁,适合数据在SwiftUI外部存储,把@ObservedObject包裹的数据作为视图的依赖,比如数据库中存储的数据,当SwiftUI视图“更新”时,实际发生的是创建并显示视图的新示例。
这意味着当您通过@ObservableObject
声明视图模型时,您将获得数据对象Model
的一个新示例