ios - SwiftUI - 使用 ForEach 时,您应该在子视图中使用 `@State var` 还是 `let`
问题描述
我认为我在理解确切@State
含义方面存在差距,尤其是在显示ForEach
循环内容时。
我的场景:我创建了最小的可重现示例。下面是一个带有ForEach
循环的父视图。每个子视图都有一个NavigationLink
.
// Parent code which passes a Course instance down to the child view - i.e. CourseView
struct ContentView: View {
@StateObject private var viewModel: ViewModel = .init()
var body: some View {
NavigationView {
VStack {
ForEach(viewModel.courses) { course in
NavigationLink(course.name + " by " + course.instructor) {
CourseView(course: course, viewModel: viewModel)
}
}
}
}
}
}
class ViewModel: ObservableObject {
@Published var courses: [Course] = [
Course(name: "CS101", instructor: "John"),
Course(name: "NS404", instructor: "Daisy")
]
}
struct Course: Identifiable {
var id: String = UUID().uuidString
var name: String
var instructor: String
}
实际困境:我尝试了 的两种变体CourseView
,一种是let
常量,另一种@State var
是course
字段。下面代码中的附加注释。
let
当导航链接打开时,带有常量的那个会成功更新子视图。但是,带有的那个@State var
不会更新视图。
struct CourseView: View {
// Case 1: Using let constant (works as expected)
let course: Course
// Case 2: Using @State var (doesn't update the UI)
// @State var course: Course
@ObservedObject var viewModel: ViewModel
var body: some View {
VStack {
Text("\(course.name) by \(course.instructor)")
Button("Edit Instructor", action: editInstructor)
}
}
// Case 1: It works and UI gets updated
// Case 2: Doesn't work as is.
// I've to directly update the @State var instead of updating the clone -
// which sometimes doesn't update the var in my actual project
// (that I'm trying to reproduce). It definitely works here though.
private func editInstructor() {
let instructor = course.instructor == "Bob" ? "John" : "Bob"
var course = course
course.instructor = instructor
save(course)
}
// Simulating a database save, akin to something like GRDB
// Here, I'm just updating the array to see if ForEach picks up the changes
private func save(_ courseToSave: Course) {
guard let index = viewModel.courses.firstIndex(where: { $0.id == course.id }) else {
return
}
viewModel.courses[index] = courseToSave
}
}
我正在寻找的是需要循环遍历模型数组并且模型从子视图中在 DB 中更新的场景的最佳实践。
解决方案
这是适合您的正确方法,不要忘记我们不需要在 View 中放置逻辑!视图应该是虚拟的!
struct ContentView: View {
@StateObject private var viewModel: ViewModel = ViewModel.shared
var body: some View {
NavigationView {
VStack {
ForEach(viewModel.courses) { course in
NavigationLink(course.name + " by " + course.instructor, destination: CourseView(course: course, viewModel: viewModel))
}
}
}
}
}
struct CourseView: View {
let course: Course
@ObservedObject var viewModel: ViewModel
var body: some View {
VStack {
Text("\(course.name) by \(course.instructor)")
Button("Update Instructor", action: { viewModel.update(course) })
}
}
}
class ViewModel: ObservableObject {
static let shared: ViewModel = ViewModel()
@Published var courses: [Course] = [
Course(name: "CS101", instructor: "John"),
Course(name: "NS404", instructor: "Daisy")
]
func update(_ course: Course) {
guard let index = courses.firstIndex(where: { $0.id == course.id }) else {
return
}
courses[index] = Course(name: course.name, instructor: (course.instructor == "Bob") ? "John" : "Bob")
}
}
struct Course: Identifiable {
let id: String = UUID().uuidString
var name: String
var instructor: String
}
推荐阅读
- c# - 特定 XML 元素的正则表达式
- azure - 当 azure 函数从队列中拉出消息后抛出异常时会发生什么?
- android - 在启动 Intent 之前检查 editText 的值
- tinymce - 如何更改 TinyMCE 图像插件中的属性文本?
- r - lapply 与分裂向量
- python - 在 matplotlib 注释文本中增加行分隔
- c# - 图像上的 WPF 链效果
- android - 无法在命令行中访问 C:\Program 文件
- java - 如果从 jar 运行,Java 类未找到异常,如果从 IntelliJ 运行则运行良好
- laravel - 停止在 Laravel 中渲染 Vue 组件