1.@State
与 @Binding
的区别
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| struct ContentView: View { @State var item: Item = Item(number: 1) var body: some View { VStack { Text("\(item.number)") .padding() Divider() Button("NUMBER + 1") { item.number += 1 } .padding() Divider() SecView(item: item) .padding() } } }
struct SecView: View { @State var item: Item var body: some View { Text("\(item.number)") } }
struct Item: Identifiable { let id: UUID = UUID() var number: Int }
|
这段代码,我们需要在点击按钮时,SecView 中的数字同步 ContentView。但事实是 SecView 中的数字一直保持为 1。
此时,只要将 SecView 中的 @State
改为 @Binding
即可:
1 2 3 4 5 6 7 8
| struct SecView: View { @Binding var item: Item var body: some View { Text("\(item.number)") } }
|
2.@ObservableObject
, @StateObject
大学习
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| struct ContentView: View { @ObservedObject var itemManager: ItemManager = ItemManager(items: [ Item(number: 1), Item(number: 2) ]) var body: some View { VStack { ForEach(itemManager.items) { item in HStack{ Text("\(item.number)") Button("NUMBER + 1") { itemManager.add(id: item.id) } } } Button("Create Item") { itemManager.createItem() } Divider() ForEach(itemManager.items) { item in SecView(item: item) } } } }
struct SecView: View { @State var item: Item var body: some View { Text("\(item.number)") } }
struct Item: Identifiable { let id: UUID = UUID() var number: Int init(number: Int) { self.number = number } mutating func add() { self.number += 1 } }
class ItemManager: ObservableObject { @Published var items: [Item] init(items: [Item]) { self.items = items } func add(id: UUID) { if let index = items.firstIndex(where: { $0.id == id }) { items[index].add() } } func createItem() { items.append(Item(number: 3)) } }
|
这段代码,我们需要
- 点击「NUMBER + 1」按钮,第一个 ForEach 对应的数字 +1;
- 同时,第二个 ForEach 中对应 SecView 的数字也 +1;
- 点击「Create Item」按钮,2 个 ForEach 都会新增 Item。
但上述代码只能实现 1、3,SecView 中的数字不会变化。
尝试一:将 @ObservedObject
调整为 @StateObject
(失败)
1 2 3 4
| @ObservedObject var itemManager: ItemManager = ItemManager(items: [ Item(number: 1), Item(number: 2) ])
|
@ObservedObject
与 @StateObject
在本例中没有区别。
@StateObject
与 @ObservedObject
的区别
@StateObject
与 @ObservedObject
基础作用,这里不再解释,可看:
- 探讨 SwiftUI 中的关键属性包装器:@State、@Binding、@StateObject、@ObservedObject、@EnvironmentObject 和 @Environment
- SwiftUI 数据绑定详解:ObservableObject、@Published 与 @StateObject
之前我遇到过在使用 @ObservedObject
声明 Model 后,随着 View 的刷新,Model 的数组会被清空。
具体来说可以这样表述:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| class People: ObservableObject { @Published var age = 0 }
struct ObservedView: View { @ObservedObject var people = People() var body: some View { VStack{ Text("\(people.age)") Button(action: {people.age = people.age + 1}) { Text("update (@ObservedObject)") } } } }
struct StateView: View { @StateObject var people = People() var body: some View { VStack{ Text("\(people.age)") Button(action: {people.age = people.age + 1}) { Text("update (@StateObject)") } } } }
struct ContentView: View { @State var count = 0 var body: some View { VStack(spacing: 50){ VStack{ Text("\(count)") Button(action: {count = count + 1} ) { Text("update") } } ObservedView() StateView() } } }
|
参考:#10 @ObservedObject的使用
在点击 ContentView 的 Button 后,ObservedView 的计数会重置,而 StateView 的计数并不会重置。等于是当更新 ContentView 的时候,原本在 ObservedView 的 people 对象又被重新生成了一次。
简单说,@ObservedObject
不管存储,会随着 View
的创建被多次创建。而 @StateObject
保证对象只会被创建一次。因此,如果是在 View
里自行创建的 ObservableObject
model 对象,大概率来说使用 @StateObject
会是更正确的选择。@StateObject
基本上来说就是一个针对 class 的 @State
升级版。
对于 View
自己创建的 ObservableObject
状态对象来说,极大概率你可能需要使用新的 @StateObject
来让它的存储和生命周期更合理:
具体可参考:@StateObject 和 @ObservedObject 的区别和使用
尝试二:将 Item 调整为 Class (成功)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| struct ContentView: View { @ObservedObject var itemManager: ItemManager = ItemManager(items: [ Item(number: 1), Item(number: 2) ]) var body: some View { VStack { ForEach(itemManager.items) { item in HStack{ Text("\(item.number)") Button("NUMBER + 1") { itemManager.add(id: item.id) } } } Button("Create Item") { itemManager.createItem() } Divider() ForEach(itemManager.items) { item in SecView(item: item) } } } }
struct SecView: View { @ObservedObject var item: Item var body: some View { Text("\(item.number)") } }
class Item: Identifiable, ObservableObject { let id: UUID = UUID() @Published var number: Int init(number: Int) { self.number = number } func add() { self.number += 1 } }
class ItemManager: ObservableObject { @Published var items: [Item] init(items: [Item]) { self.items = items } func add(id: UUID) { items.first(where: { $0.id == id })?.add() self.objectWillChange.send() } func createItem() { items.append(Item(number: 3)) } }
|
注意,如果在 add()
中不添加 self.objectWillChange.send()
,那么第一个 ForEach 中的数字不会变化。
这里实际讨论了两种更新 ObservableObject 的方法,参考: Why is an ObservedObject array not updated in my SwiftUI application?
使用 objectWillChange.send()
,对应到第一个 ForEach
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| class Person: ObservableObject,Identifiable { var id: Int @Published var name: String init(id: Int, name: String){ self.id = id self.name = name } }
class People: ObservableObject { @Published var people: [Person] init(){ self.people = [ Person(id: 1, name:"Javier"), Person(id: 2, name:"Juan"), Person(id: 3, name:"Pedro"), Person(id: 4, name:"Luis")] } }
struct ContentView: View { @ObservedObject var mypeople: People var body: some View { VStack{ ForEach(mypeople.people){ person in Text("\(person.name)") } Button(action: { self.mypeople.people[0].name="Jaime" self.mypeople.objectWillChange.send() }) { Text("Add/Change name") } } } }
|
对 Identifiable
类使用 @ObservedObject
,对应到第二个 ForEach
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| struct PersonRow: View { @ObservedObject var person: Person
var body: some View { Text(person.name) } }
struct ContentView: View { @ObservedObject var mypeople: People
var body: some View { VStack{ ForEach(mypeople.people){ person in PersonRow(person: person) } Button(action: { self.mypeople.people[0].name="Jaime" }) { Text("Add/Change name") } } } }
|
@StateObject
是对的
当然,最简单的还是使用 @StateObject
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| struct Person: Identifiable { var id: Int var name: String
init(id: Int, name: String){ self.id = id self.name = name }
}
class Model: ObservableObject { @Published var people: [Person]
init(){ self.people = [ Person(id: 1, name:"Javier"), Person(id: 2, name:"Juan"), Person(id: 3, name:"Pedro"), Person(id: 4, name:"Luis")] }
}
struct ContentView: View { @StateObject var model = Model()
var body: some View { VStack{ ForEach(model.people){ person in Text("\(person.name)") } Button(action: { self.mypeople.people[0].name="Jaime" }) { Text("Add/Change name") } } } }
|
当然,在我们自己的案例中 @StateObject
是不中用的——第二个 ForEach 无论如何是无法刷新的。
@Observable
, @Bindable
新的就是好的
参考:
- Swift 5.9 新 @Observable 对象在 SwiftUI 使用中的陷阱与解决
- SwiftUI5 新增加的Observable宏的基本用法。
- SwiftUI 属性包装器系列 — @Observable @Bindable
- 深入理解 Observation - 原理,back porting 和性能
- 深度解读 Observation —— SwiftUI 性能提升的新途径
- 新框架、新思维:解析 Observation 和 SwiftData 框架
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| import SwiftUI import Observation
struct ContentView: View { @State var itemManager: ItemManager = ItemManager(items: [ Item(number: 1), Item(number: 2) ]) var body: some View { VStack { ForEach(itemManager.items) { item in HStack{ Text("\(item.number)") Button("NUMBER + 1") { itemManager.add(id: item.id) } } } Button("Create Item") { itemManager.createItem() } Divider() ForEach(itemManager.items) { item in SecView(item: item) } } } }
struct SecView: View { @Bindable var item: Item var body: some View { Text("\(item.number)") } }
@Observable class Item: Identifiable { let id: UUID = UUID() var number: Int init(number: Int) { self.number = number } func add() { self.number += 1 } }
@Observable class ItemManager { var items: [Item] init(items: [Item]) { self.items = items } func add(id: UUID) { items.first(where: { $0.id == id })?.add() } func createItem() { items.append(Item(number: 3)) } }
|
请注意:您永远不会在 @Binding
和 @Bindable
之间进行选择。@Binding
属性包装器表明视图上的某些状态由父视图拥有,并且您对基础数据具有读写访问权限。@Bindable
表示用于创建与符合 Observable 协议的数据模型对象的可变属性的绑定;
另外,使用 SwiftData 时 @Model
与 @Bindable
配合也能实现相同的效果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| @Model class Item: Identifiable { var id: UUID var value: Int init(id: UUID, value: Int) { self.id = id self.value = value } }
struct ContentView: View { @Query private var items: [Item] @State var selectItem: Item? var body: some View { List { ForEach(items) { item in Text("\(item.value)") .onTapGesture { selectItem = item print(item.value) } } } .sheet(item: $selectItem) { item in ItemDetailView(item: item) } } }
struct ItemDetailView: View { @Bindable var item: Item var body: some View { VStack() { Text("\(item.value)") Button(action: { item.value = 999 }, label: { Text("Change Value") }) } } }
|