SwiftUI - 使用 ForEach 时,您应该在子视图中使用 `@State var` 还是 `let`
Posted
技术标签:
【中文标题】SwiftUI - 使用 ForEach 时,您应该在子视图中使用 `@State var` 还是 `let`【英文标题】:SwiftUI - Should you use `@State var` or `let` in child view when using ForEach 【发布时间】:2021-12-24 15:38:35 【问题描述】:我认为我在理解 @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
字段。下面代码中的其他 cmets。
当导航链接打开时,带有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 中更新的场景的最佳实践。
【问题讨论】:
您只是在使用 var 进行初始化,这就是问题所在,让您推送视图以进行更新,您可以使用 let 或使用绑定!取决于您的孩子是否会更新该值。 你应该使用@Binding @swiftPunk 帮助我理解。因此,当父数组更新时,let
版本将获取新值。但是为什么@State
不这样做呢?那里有点困惑。
@JoakimDanielson 在子视图中确实发生了很多变化,而父视图并没有更新那么多。我倾向于让方法。
@SiddharthKamaria:如果你在苹果视频中注意到State
他们都使用private
这意味着任何用State
包裹的值它是一种独立且自动的,只对属于的视图负责,有时我们可以初始化 State 值但我们只是初始化它!之后,如果我们尝试重新初始化,它不会被初始化,因为它已经被初始化了!除非我们杀死视图并重新初始化
【参考方案1】:
这是一个正确的方法,不要忘记我们不需要在 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
【讨论】:
还有一件事。注册您对 Apple 视频的评论 - 我同意State
应该是私有的,但我面临一个问题,即即使从子视图中修改状态也不会更新状态。它是一种命中或失败,有时有效,有时无效。
如果您更新视图的状态,例如 100%,它将始终有效。但我认为您所说的关于 State 的问题与 NavigationView 相结合!或导航链接!您可能不知道,但那些 api 已经并且已经并且肯定会有这样的错误!如果您对 NavigationView 有此类问题,我不会感到惊讶。
哈哈哈,我遇到了臭名昭著的 14.4 或 14.5 NavigationView 关闭自身问题。但是,我认为我遇到了另一个问题,即我的状态没有更新。根据 Apple 的文档——You should only access a state property from inside the view’s body, or from methods called by it.
和我的一些方法正在从视图未直接调用的方法访问状态。以上是关于SwiftUI - 使用 ForEach 时,您应该在子视图中使用 `@State var` 还是 `let`的主要内容,如果未能解决你的问题,请参考以下文章
从 ForEach 中引用变量时,SwiftUI“无法推断通用参数'数据'”
使用 ForEach 中的 SwiftUI 3.0 .swipeActions,您如何在将 ForEach 的输入参数传递给该视图时让一个动作转到另一个视图?