SwiftUI_2_属性包装器

参考:

https://blog.csdn.net/weixin_45727359/article/details/109108544

https://zhuanlan.zhihu.com/p/141229504?from_voters_page=true

用 @EnvironmentObject 从环境中读取值

SwiftUI: 全局状态管理

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、值传递

MapView从父视图传递count值给内部count1,是值类型的传递。正如普通的Swift函数传递参数一样,是值的拷贝,修改MapView的count1,不会影响ContentView的count值。
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)

  

posted @ 2022-03-24 15:46  素染年华  阅读(133)  评论(0编辑  收藏  举报