解决方案
import SwiftUI
struct StudentsView: View {
// Here, we make the `StudentsView` instance subscribe to the
// `StudentRegister` instance's `objectWillChange` publisher, by wrapping
// the `studentRegister` instance-property with the `@StateObject` (**not**
// the `@State`) property wrapper.
//
// This, combined with the fact that the `students` instance-property of the
// `StudentRegister` class has been wrapped with the `@Published` property
// wrapper, will cause the `StudentsView` instance to be re-rendered
// whenever we add, remove, or re-order the **references** to `Student`
// instances that are being stored in the `StudentRegister` instance's
// `students` Array instance-property.
@StateObject var studentRegister = StudentRegister()
@State private var isLoading = true
let layout = [
GridItem(.flexible()),
GridItem(.flexible()),
GridItem(.flexible())
]
var body: some View {
Group {
if isLoading {
Text("Loading...")
} else {
LazyVGrid(columns: layout, spacing: 20) {
ForEach(studentRegister.students, id: \.self) { student in
StudentView(student: student)
// Changes to the values of properties of the
// `Student` instances to which **references** are
// being stored in the `StudentRegister` instance's
// `students` Array instance-property **won't**
// cause the **references** that are being stored in
// that Array to change.
//
// Consequently, changes to the value of the
// `status` instance-property of any of the
// `Student` instances **won't** cause the
// `StudentsView` View to be re-rendered.
//
// Thus, it would be **unsafe** for us to set the
// `foregroundColor` here in this View, given the
// fact that its value is dependent on the `status`
// property of one of the `Student` instances.
// .foregroundColor(student.status) // **Don't** do this.
}
}
}
}
.onAppear {
// Note: This could obviously be improved with
// asynchronous-loading in the future.
studentRegister.load()
isLoading = false
}
}
}
class StudentRegister: ObservableObject {
@Published var students = [Student]()
func load() {
students = [.init(name: "Bob Smith", status: .blue), .init(name: "Alice Davidson", status: .yellow)]
}
}
struct StudentView: View {
// The use of the `@ObservedObject` property wrapper here, **will** cause
// the `StudentView` instance to subscribe to the `Student` instance's
// `objectWillChange` publisher.
@ObservedObject var student: Student
var body: some View {
VStack() {
Button(action: {
student.status = Color.green
}) {
Text(student.name)
}
// Given that changes to the value of `student.status` will cause
// **this** `StudentView` instance to be re-rendered, it's safe for
// us to set the `foregroundColor` (which depends on the value of
// `student.status`) here in **this** View.
.foregroundColor(student.status)
}
}
}
class Student: ObservableObject, Hashable, Equatable {
var name: String
@Published var status: Color
init(name: String, status: Color) {
self.name = name
self.status = status
}
static func == (lhs: Student, rhs: Student) -> Bool {
return lhs.name == rhs.name
}
func hash(into hasher: inout Hasher) {
hasher.combine(name)
}
}
struct StudentsView_Previews: PreviewProvider {
static var previews: some View {
StudentsView()
}
}
解决方案说明
将ObservableObject 的实例存储在“拥有”该实例的视图中
您应该将@StateObject 用于最高级别视图中的属性,该属性包含符合ObservableObject 协议的类的特定实例。那是因为包含该属性的视图“拥有”该实例。
在低级视图中接收ObservableObject 的实例
您应该将@ObservedObject 用于该实例直接传递到的低级视图中的属性,或者如果您选择通过传递实例将实例间接传递到低级视图作为调用“拥有”实例的视图的body 计算属性变量中的environmentObject 视图方法的参数,您应该将@EnvironmentObject 用于需要接收的较低级别视图中的属性它。
哪些变化会导致哪些 ObservableObject 的 objectWillChange Publishers 被触发,哪些视图将因此被重新渲染。
如果您在studentRegister.students 数组中添加、删除或重新排序元素,则会导致StudentRegister 实例的objectWillChange Publisher 触发,因为它的students 属性是@Published 属性,并且在它存储的数组中添加、删除或重新排序元素会导致该数组包含的 references/pointers 到 Student 实例发生变化。这反过来将触发 StudentsView 视图被重新渲染,因为它订阅了 StudentRegister 实例的 objectWillChange 发布者,因为它将对该实例的引用存储在 @StateObject 或 @ObservedObject 中或@EnvironmentObject 属性(它专门将其存储在@StateObject 中,因为它恰好“拥有”该实例)。
请务必注意,studentRegister.students 数组将 references/pointers 存储到 Student 实例,因此,对任何 Student 实例的属性的更改赢了t 导致studentRegister.students 数组的元素发生变化。由于这些Student 实例的status 属性之一的更改不会导致studentRegister.students 数组更改,它也不会导致studentRegister 对象的objectWillChange Publisher 被触发,因此不会触发StudentsView 视图被重新渲染。
更改Student 实例的status 属性之一将 导致Student 实例的objectWillChange Publisher 被触发,因为status 属性是@Published 属性,因此对该属性的更改将触发 Student 实例对应的 StudentView 视图重新渲染。请记住,就像StudentsView 视图订阅StudentRegister 实例的objectWillChange 发布者一样,StudentView 视图订阅了它的Student 实例的objectWillChange 发布者,因为它将对该实例的引用存储在@ 987654368@ 或 @ObservedObject 或 @EnvironmentObject(它专门将其存储在 @ObservedObject 中,因为它不“拥有”Student 实例,而是由其直接传递给它直接父视图)。