回顾上一篇文章
操作值类型
当数据是一个值类型的时候 (比如 struct,enum 或者是一个不可变对象),我们有三种选择:
- 使用普通的属性,
- 使用 @State 属性,
- 或者使用 @Binding 属性。
当一个 view 按照只读的方式 来使用这个值时,普通的属性就够了, 我们可以把这个值通过初始化的方式被传入,如果想要以不同的值渲染同样的View的话,只需 要传入另外的值就行了。
struct ContentView: View {
var content: String
var body:some View {
VStack{
Text(content)
}
}
}
@State存储值类型。它会创建一个由 SwiftUI 进行管理的可读可写属性。当 view 被创建前,SwiftUI 会初始化 @State 属性,并且当 view 被创建时,属性值会被持久地保存到内存中,@State 总是只存在于当前的 view 树中的,你不应该从 view 层级的上层访问 (或者甚至初始化) 一个 state 变量。因此,我们会习惯于将一个 @State 变量标记为私有。这一限制 (不能随意访问或初始化) 没有在文档中写明,但是在 Swift 论坛上是被提到过的。
struct ContentView: View {
@State private var content = "daming"
var body:some View {
VStack {
Text(content)
Button("改内容") {
self.content = "daming"
}
}
}
}
当一个 view 需要对某个存储在别的地方的值进行读写时,可以用 @Binding 进行声明,绑定 (binding) 是信息源的延伸:比如它可能来源于一个 @State 或一个 ObservableObject (可观察对象)。它为 view 提供获取、设置和观察底层值的方法。换句话说,这个值不属于 view,SwiftUI 中的大部分可以对值进行更改的组件,都是以绑定的方式来获取这个值的:比如 TextView 中的文本属性,以及 Slider 的滑块值,都是绑定。
struct ContentView: View {
@State private var content = "daming"
var body:some View {
VStack{
MultilineTextField($content)
}
}
}
struct MultilineTextField: View {
@Binding var content: String
}
ObservableObject
操作对象
在几乎所有的实际 app 中,我们会使用到我们自己的模型对象。为了让我们的模型对象可以被 SwiftUI 观察到,它的类型需要遵守被定义在 Combine 框架中的 ObservableObject 协议。
举例来说,当我们创建一个地址簿的 app 时,我们会需要一个代表联系人的 Contact 类:
class Contact: ObservableObject, Identifiable {
let id = UUID()
@Published var name: String
@Published var city: String
init(name: String, city: String) {
self.name = name
self.city = city
}
}
ObservableObject 协议的唯一要求是实现 objectWillChange,它是一个 publisher,会在对象变更时发送事件。通过在 name 和 city 属性前面添加 @Published,框架会为我们创建一个 objectWillChange 的实现,在每次这两个属性发生改变的时候发送事件。
public protocol ObservableObject : AnyObject {
associatedtype ObjectWillChangePublisher : Publisher = ObservableObjectPublisher where Self.ObjectWillChangePublisher.Failure == Never
var objectWillChange: Self.ObjectWillChangePublisher {
get
}
}
我们需要使用 @ObservedObject 来定义 Detail 中的 contact 属性
struct Detail: View {
@ObservedObject var contact: Contact = Contact.init(name: "daming", city: "beijing")
var body: some View {
VStack {
Text(contact.name)
Text(contact.city)
Button("变身") {
self.contact.name = "Superhero \(self.contact.name)"
}
}
}
}
struct ContentView: View {
@State var count = 0
var body: some View {
VStack{
Text("\(count)")
Button("+1") {
count += 1
}
Detail().padding()
}
}
}
当我们点击变身时候,我们如愿以偿的得到了 Superhero,目前来说看似是没有任何问题,但是当我们点击ContentView的button时候我们发现什么,我们的Superhero不见了,这并不是我们想要的结果。
@StateObject
正常开发中,我们只想改变ContentView中Text的内容,并不想重制Detail内容,我们需要用@StateObject进行优化。
struct Detail: View {
@StateObject var contact: Contact = Contact.init(name: "daming", city: "beijing")
var body: some View {
VStack {
Text(contact.name)
Text(contact.city)
Button("变身") {
self.contact.name = "Superhero \(self.contact.name)"
}
}
}
}
ContentView button 点击之后Text的内容加1,我们的英雄还在。
ObservedObject 不管存储,会随着 View 的创建被多次创建。而 @StateObject 保证对象只会被创建一次。因此,如果是在 View 里自行创建的 ObservableObject model 对象,大概率来说使用 @StateObject 会是更正确的选择。@StateObject 基本上来说就是一个针对 class 的 @State 升级版。