【问题标题】:SwiftUI - Should you use `@State var` or `let` in child view when using ForEachSwiftUI - 使用 ForEach 时,您应该在子视图中使用 `@State var` 还是 `let`
【发布时间】: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 值但我们只是初始化它!之后,如果我们尝试重新初始化,它不会被初始化,因为它已经被初始化了!除非我们杀死视图并重新初始化

标签: ios swift swiftui swiftui-navigationlink


【解决方案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. 和我的一些方法正在从视图未直接调用的方法访问状态。
猜你喜欢
  • 2021-12-24
  • 1970-01-01
  • 1970-01-01
  • 2011-10-17
  • 1970-01-01
  • 2014-03-21
  • 1970-01-01
  • 1970-01-01
  • 2020-12-20
相关资源
最近更新 更多