SwiftUI(八)- 绑定对象与环境查询
引言
在SwiftUI中,视图与数据的绑定构建起了一种高度相应式的开发体验。当数据发生改变时,视图会自动更新,而开发者可以专注于描述UI的状态,而无需关心视图的更新机制。要实现这样的响应式效果,SwiftUI提供了三种主要的状态管理工具:@State、@ObservableObject和@EnvironmentObject。它们不仅为单个视图提供了轻量的状态存储,还支持复杂的应用中视图之间的无缝数据共享。在本文中,我们将探讨这些关键属性包装器的用途,并学习如何在SwiftUI中优雅地管理和共享状态。
@State、@ObservableObject和@EnvironmentObject的区别
状态在任何现代应用中都是十分重要的,但在SwiftUI中我们需要牢记,所有的是视图只是其状态的函数,我们并不直接改变视图,而是操作状态,让状态来决定显示的结果。
SwiftUI为我们提供了几种在应用中存储状态的方法,但它们之间有些差异,理解这些差异对正确使用框架至关重要。
@State
到目前为止我们经常用的有@State,用它来创建属性:
struct ContentView: View {@State var score = 0....
}
这在视图内创建了一个整型的属性,但它使用@State属性包装器请求了SwiftUI对该属性进行内存管理。
这很重要,我们的所有视图都是结构体,这意味着它们不能被改变,而当我们使用@State来创建一个属性时,我们将对它的控制权交给了SwiftUI,当该状态发生变化时,SwiftUI会自动重新加载视图,以显示最新的更改。
@State非常适合那些属于特定视图并且基本不会在该视图之外使用的简单属性,因此通常建议将这个属性标记为私有属性:
@State private var score = 0
@ObservableObject
而对于一个更加复杂的属性,比如你有一个自定的类型想要使用,并且该类型可呢个有很多个属性和方法,或者该数据可能在多个视图之间共享时,我们应该使用@ObservableObject。
它和@State非常相似,不同之处在于我们现在使用的外部引用类型,而不是简答的本地属性像字符或者整数。通过@ObservableObject我们告诉SwiftUI视图仍然依赖于会变化的数据,但是这个数据是我们自己负责管理的,我们需要自己创建这个实例,并给属性进行赋值等等操作。
@EnvironmentObject
还有第三种可以用来修饰属性的类型,那就是@EnvironmentObject。这是一个通过应用程序本身提供给视图的值,它是全局的共享数据,任何视图都可以读取。因此,如果你的应用有一些所有视图或者很多视图都需要读取的重要模型数据,比如用户信息。那我们可以选择将该数据模型从一个视图传递到另一个视图,或者直接将其放入到环境中,使每个视图都能即时访问。
总结:
- 对于单个视频的简单属性,使用@State修饰。它们通常应该标记为私有。
- 对于可能属于多个视图的复杂属性,使用@ObservableObject修饰。只要使用引用类型时都应该是@ObservableObject修饰。
- 对于应用中很多地方都会使用的属性,比如共享数据,使用@EnvironmentObject修饰。
使用@ObservableObject
首先我们需要创建一个自定义的类,并遵循ObservableObject协议,并且它的属性需要使用@Published来标记,以便在这些属性发生变化时自动发送更新通知。
import UIKit
import SwiftUICoreclass Cat:ObservableObject{@Published var name:String@Published var age:Intinit(name:String,age:Int) {self.name = nameself.age = age}func refresh(name:String,age:Int) {self.name = nameself.age = age}}
接下来在视图中使用@ObservedObject来修饰这个课观察的对象,然后通过一个点击按钮来更新这个对象的name和age属性:
import SwiftUI
import SwiftUICorestruct ContentView: View {@ObservedObject var cat = Cat(name: "Tom", age: 3)var body: some View {VStack {Text("这只小猫叫\(cat.name)")Text("今年\(cat.age)岁了")Button(action: {self.cat.refresh(name: "Jerry", age: 4)}) {Text("修改名称和年龄")}.padding()}.padding()}
}
需要注意的是:当我们修改对象的属性时,需要在主线程上进行。
使用@EnvironmentObject在视图之间共享数据
对于应该在整个应用中与所有视图共享的数据,SwiftUI为我们提供了@EnvironmentObject,这使我们能够在需要的地方共享数据模型,同时确保当数据变化时,我们的视图会自动保持更新。
我们也可以将@EnvironmentObject看作是在多个视图上使用@ObservableObject的更简答的方式。如果我们在视图A中创建一些数据,然后将其传递给视图B,再传递给视图C,最后传递给视图D来使用。而当我们使用@EnvironmentObject时,我们只需要在视图A中创建一个数据,并将其放入环境中,这样视图B、C和D都可以访问它。
需要注意的是,环境对象必须由祖先视图提供。当SwiftUI找不到正确的环境对象时可能会发生崩溃。
我们自定义的对象仍然需要遵循ObservableObject协议,并使用@Published来修饰属性:
import UIKit
import SwiftUICoreclass Cat:ObservableObject{@Published var name:String@Published var age:Intinit(name:String,age:Int) {self.name = nameself.age = age}func refresh(name:String,age:Int) {self.name = nameself.age = age}}
在使用之前,我们需要先将自己创建的实例注册到环境中。
import SwiftUI@main
struct SwiftUIDemo8App: App {@ObservedObject var cat = Cat(name: "Tom", age: 3)var body: some Scene {WindowGroup {ContentView().environmentObject(cat)}}
}
然后在使用的地方使用@EnvironmentObject进行获取。
import SwiftUI
import SwiftUICorestruct ContentView: View {@EnvironmentObject var cat: Catvar body: some View {VStack {Text("这只小猫叫\(cat.name)")Text("今年\(cat.age)岁了")Button(action: {self.cat.refresh(name: "Jerry", age: 4)}) {Text("修改名称和年龄")}.padding()}.padding()}
}
在预览中记得也要进行注册嗷:
#Preview {ContentView().environmentObject(Cat(name: "Tom", age: 3))
}
因此一旦我们将对象注册到环境中,我们就可以直接在顶层或者任何层次的视图中使用它,最重要的是,从任何地方更新该对象的属性时,所有依赖于它的视图都会自动刷新。
结语
在本文中,我们深入探讨了 SwiftUI 中的数据绑定与状态管理,通过 @State、@ObservableObject 和 @EnvironmentObject 三个重要的属性包装器,展示了如何在构建响应式用户界面时有效管理应用的状态。我们了解到,@State 适用于局部状态管理,而 @ObservableObject 则为跨多个视图共享数据提供了便利。通过 @EnvironmentObject,我们能够在更复杂的视图层次中无缝共享数据,极大地简化了状态的传递过程。
掌握这些工具,将帮助开发者在 SwiftUI 中构建更加灵活和响应迅速的应用程序。希望本文能为你的 SwiftUI 学习之旅提供实用的指导,鼓励你在实践中不断探索和创新。