SwiftUI_2_属性包装器
参考:
https://blog.csdn.net/weixin_45727359/article/details/109108544
https://zhuanlan.zhihu.com/p/141229504?from_voters_page=true
SwiftUI学习之@State、@Binding、@ObservedObject、@EnvironmentObject、@StateObject
@StateObject 和 @ObservedObject 的区别和使用
@ObservedObject, @State, and @EnvironmentObject的区别
ObservableObject研究——想说爱你不容易 ❤️
SwiftUI2.0 —— App、Scene、新的代码结构(二)
一、属性包装器(property wrapper):
@State、@Binding、@StateObject
@ObservedObject、
@EnvironmentObject
@State
在 SwiftUI 内部会被自动转换为一对 setter 和 getter,对这个属性进行赋值的操作将会触发 View 的刷新
@State修饰引用对象,修改值是无效的。
1、单一视图的本地状态:修改value,更新view的value
2、值传递,父视图value传递给子视图value1,修改value1,不影响value
3、修饰对象(引用类型),修改值(无效),view不更新
@StateObject
顾名思义,它是@State的引用类型版本。
对于@State,SwiftUI 1.0时代,如果想将引用类型作为source of truth,通常的方法是使用@EnvironmentObject或者 @ObservedObject。
@StateObject 和 @ObservedObject 的区别就是实例是否被创建其的View所持有,其生命周期是否完全可控。
由于@ObservedObject的机制问题,其创建的实例并不被当前View所拥有(当前View无法管理其生命周期),因此在一些特殊的情况下会出现不可预料的结果。
@binding
@ObservedObject
+ @binding ----- > @StateObject + @binding
它做的事情是将值语义的属性“转换”为引用语义。对被声明为 @Binding 的属性进行赋值,改变的将不是属性本身,而是它的引用,这个改变将被向外传递.
使用@binding时,注意$符号的使用(对一个由 @ 符号修饰的属性,在它前面使用 $ 所取得的值,被称为投影属性 (projection property))
@ObservedObject
从一个视图传递到另一个视图。
在ObservableObject研究——想说爱你不容易中,我们探讨过SwiftUI更倾向于我们不要创建一个沉重的Singel source of truth,而是将每个功能模块作为独立的状态机(一起组合成一个大的状态app),使用能够对生命周期和作用域更精确可控的手段创建区域性的source of truth。
@EnvironmentObject
比它们都更进一步:可以把一个对象注入环境,以便任何子视图都可以自动获得该对象的访问能力
@ObservedObject的另一个问题就是,view之间数据的共享是通过class作为引用数据类型的特点来传递。也就是说不同view之间,必须引用同一个class实例。那么这样就会造成一个问题,比如,有A B C D 四个view, A里边有个navigationLink指向B, B指向C,C指向D,如果仅仅A和D想通过@ObservedObject来共享数据的话,那么这个ObservedObject必须从A传递到B,然后到C到D。所以这种情况就需要用到@EnvironmentObject。
二、应用
@State
1、基本使用
struct User {
var firstName = "Bilbo"
var lastName = "Baggins"
}
struct ContentView: View {
@State private var user = User() //1
var body: some View {
VStack {
Text("Your name is \(user.firstName) \(user.lastName).") //2
TextField("First name", text: $user.firstName) //3
TextField("Last name", text: $user.lastName)
}
}
}
2、值传递
struct ContentView: View { @State var count = "eeer" var body: some View { VStack{ MapView(count1: count) } Text(count) .padding() } } struct MapView: View{ @State var count1: String var body: some View { Button("点我") { //添加一个按钮,指定标题文字为 First button self.count1 = "1234567890" } } }
@Binding
@Binding的作用是在保存状态的属性和更改数据的视图之间创建双向连接
将当前属性连接到存储在别处的单一数据源(single source of truth)
struct Product:Identifiable { var isFavorited:Bool var title:String var id: String } struct FilterView: View { @Binding var showFavorited: Bool //3 var body: some View { Toggle(isOn: $showFavorited) { //4 Text("Change filter") } } } struct ProductsView: View { let products: [Product] = [ Product(isFavorited: true, title: "ggggg",id: "1"), Product(isFavorited: false, title: "3333",id: "2")] @State private var showFavorited: Bool = false //1 var body: some View { List { FilterView(showFavorited: $showFavorited) //2 ForEach(products) { product in if !self.showFavorited || product.isFavorited { Text(product.title) } } } } }
@StateObject 和 @ObservedObject
@StateObject 和 @ObservedObject 的区别就是实例是否被创建其的View所持有,其生命周期是否完全可控。
@ObservedObject创建的实例生命周期可能长于/短于/等于当前的View的生命周期
三段代码,三种结果,这也就是为什么苹果要新增@StateObject的原因——让开发者可以明确地了解并掌握实例的生命周期,消除不确定性!
苹果使用@StateObject一方面修复了之前的隐患,同时通过SwiftUI2.0众多新特性的引入,进一步完善了Data Flow的实现手段。
SwiftUI 2.0 —— @StateObject 研究
class User: ObservableObject { @Published var firstName = "Bilbo" @Published var lastName = "Baggins" } struct ContentView: View { @ObservedObject private var user = User() //@ObservedObject
属性包装器只能用于符合ObservableObject
协议的类型 var body: some View { VStack { Text("Your name is \(user.firstName) \(user.lastName).") TextField("First name", text: $user.firstName) TextField("Last name", text: $user.lastName) } } }
@EnvironmentObject
@EnvironmentObject
属性包装器是说明属性的数据是来自环境,而不是在本地创建的:
class User: ObservableObject { @Published var name = "Taylor Swift" } struct EditView: View { @EnvironmentObject var user: User var body: some View { TextField("Name", text: $user.name) } } struct DisplayView: View { @EnvironmentObject var user: User var body: some View { Text(user.name) } } struct ContentView: View { let user = User() var body: some View { VStack { EditView().environmentObject(user) DisplayView().environmentObject(user) } } }
把 ContentView
改成下面这样:你会发现结果一样。我们现在是把 user
放到 ContentView
的环境中,但是因为 EditView
和 DisplayView
都是 ContentView
的子视图,所以它们自动继承了 ContentView
的环境。
VStack { EditView() DisplayView() } .environmentObject(user)