在使用 Core Data 的 SwiftUI 应用程序中,我在顶层视图中有一个
FetchRequest
:
@FetchRequest(fetchRequest: Student.fetchRequest())
private var students: FetchedResults<Student>
然后我有一些从中派生的计算属性数组
FetchResults
:
private var year7Students: [Student] {
return students.filter { $0.grade == 7 }
}
private var year8Students: [Student] {
return students.filter { $0.grade == 8 }
}
...
如果我在
View
上做一个 List
和一个 students: FetchedResults
,像这样:
var body: some View {
NavigationView {
List {
Section("All Students") {
ForEach(students) { student in
NavigationLink(destination: StudentDetailView(student: student) {
StudentCardView(student: student)
}
}
}
}
}
}
然后显示所有学生,当我更新一个(通过详细视图)时,视图会自动更新。
但是当我使用
year7Students: [Student]
等做同样的事情时,像这样:
List {
Section("Year 7 Students") {
ForEach(year7Students) { student in
NavigationLink(destination: StudentDetailView(student: student) {
StudentCardView(student: student)
}
}
}
Section("Year 8 Students") {
ForEach(year8Students) { student in
NavigationLink(destination: StudentDetailView(student: student) {
StudentCardView(student: student)
}
}
}
...
}
然后它在每个列表中显示相关学生,但是在详细视图中所做的更新不会导致当导航堆栈弹出回到该视图时更新列表中的数据,我猜这意味着视图刷新不是' t 被触发。
当底层
students
属性中的对象发生变化时,我该怎么做才能使基于计算属性数组的视图做出反应?
或者我可以使用什么不同的方法来达到相同的效果?
比标准库过滤更快的是直接在 Core Data 中使用谓词进行过滤。
FetchedResults
具有 nsPredicate
属性。设置时它会更新关联的属性
一个简单的解决方案是添加一个
State
属性grade
@State private var grade = 0
在视图的
onChange
修饰符中更改谓词。您可以定义一个值(此处为 0)来显示所有记录
.onChange(of: grade) { newGrade in
students.nsPredicate = newGrade == 0
? nil
: NSPredicate(format: "grade = %ld", newGrade)
}
您可以尝试不同的方法,直接在
filter
中使用List
,而不是
创建单独的计算变量,
就像在这个示例代码中一样:
struct Student: Identifiable {
let id = UUID()
var name: String
var grade: Int
}
struct ContentView: View {
@State var gradeFilter: Int = 0
// for testing
@State var students = [Student(name: "student-1", grade: 1),
Student(name: "student-2", grade: 7),
Student(name: "student-3", grade: 7),
Student(name: "student-4", grade: 8)]
var body: some View {
VStack {
List(students.filter {$0.grade == gradeFilter}) { student in
Text(student.name)
}
HStack {
Button("set filter to 7") { gradeFilter = 7 }
Spacer()
Button("set filter to 8") { gradeFilter = 8 }
}
HStack {
Button("add a student with grade 7") {
students.append(Student(name: "student-7", grade: 7))
}
Spacer()
Button("add a student with grade 8") {
students.append(Student(name: "student-5", grade: 8))
}
}
}.buttonStyle(.bordered)
.padding()
}
}
注意,可以使用计算的var,当某处添加新的
student
时,View
将被更新,
只要students
是State
或StateObject/ObservedObject
(CoreData对象是ObservedObject),
也就是说,正在被视图观察。
类似地,如果改变了其他东西,比如
@State var gradeFilter
,
视图将被更新。
例如:
var filteredStudents: [Student] {
students.filter { $0.grade == gradeFilter }
}
//...
List(filteredStudents) { student in
Text(student.name)
}
//...
根据我的评论,我做了一些代码测试。您需要过滤器以 FetchedResults 类型返回。然后它们是托管对象上下文中的对象,并且将获取更新。为此,您需要运行另一个应用 NSPredicate 作为过滤器的请求。这实际上不会从“数据库”再次加载,因为值应该已经缓存在上下文中。
您可以使用主要的 FetchRequest,但也可以使用另一个变量作为过滤变量,以防您同时在其他地方使用完整列表。
var studentsByGrade: @FetchRequest( sortDescriptors: [] , predicate: nil) private var subSet: FetchedResults<Item>
func getStudentsByGrade(_ grade: String) {
let predicate = NSPredicate(format: "grade == %@" , grade )
studentsByGrade.nsPredicate == predicate
}
这将更新谓词(过滤器)并使其刷新(这将被缓存得如此之快)。关于 FetchRequest.predicate 的 Apple 文档的链接 - https://developer.apple.com/documentation/swiftui/fetchrequest/configuration/nspredicate
字符串有点笨重,因为核心数据似乎依赖于很多对象和字符串......
好吧,原来我的问题与数组无关。
问题出在
StudentCardView
,我有:
let student: Student
此视图中没有任何内容告诉它根据 Student 对象的更改更新自身。
改成:
@ObservedObject var student: Student
让一切开始工作。因此,View 似乎观察到基于
FetchResults
计算属性的数组,而无需做任何额外的工作。 🤷🏻u200d♂️