如何从列表元素更新全局状态?

How to update global state from a list element?

我正在构建一个电影跟踪应用程序,我想从电影的详细信息页面更改电影的观看状态。有一个电影列表。在按下一个元素时,我将电影对象传递给详细视图并尝试更改其中的观看状态。

MovieListView.swift 文件:

struct MovieListView: View {
    @EnvironmentObject var movies: Movies

    var body: some View {
        List(movies.allMovies, id: \.title) { movie in
           NavigationLink(
            destination: MovieView(movie: movie)) {
            MovieItem(movie: movie)
           }
        }.navigationTitle("Movie List")
    }
}

MovieItem 是列表项组件,MovieView 是电影的详细信息屏幕。

MovieView.swift 文件

struct MovieView: View {

    let movie: Movie
        
    var body: some View {
        
        VStack{
            Text(movie.title)            
            Text(movie.summary)

            Toggle("Watched", isOn: $movie.watched)            

        }
    }
}

在这个例子中 isOn: $movie.watched 给出了这个错误:Cannot find '$movie' in scope

我试过绑定,但由于我传递给 MovieView 的电影对象不是状态,所以绑定不起作用。

您需要提供 @Binding。对于项目列表,这意味着您需要一些方法来生成 @Binding 以便 child 视图可以更新原始列表。您可以在 bindingForId 函数中的以下代码中看到这一点。

class Movies : ObservableObject {
    @Published var allMovies : [Movie] = [Movie(watched: false, title: "Movie 1", summary: "Summary 1"),Movie(watched: false, title: "Movie 2", summary: "Summary 2")]
    
    func bindingForId(id: UUID) -> Binding<Movie> {
        .init { () -> Movie in
            self.allMovies.first(where: { [=10=].id == id }) ?? Movie(watched: false, title: "", summary: "")
        } set: { (newValue) in
            self.allMovies = self.allMovies.map { movie in
                if movie.id == newValue.id { return newValue }
                return movie
            }
        }

    }
}

struct Movie {
    var id = UUID()
    var watched: Bool
    var title: String
    var summary : String
}

struct ContentView: View {
    @ObservedObject var movies: Movies = Movies()
    
    var body: some View {
        NavigationView {
            List(movies.allMovies, id: \.id) { movie in
                NavigationLink(
                    destination: MovieView(movie: movies.bindingForId(id: movie.id))) {
                    Text(movie.title)
                }
            }.navigationTitle("Movie List")
        }
    }
}

struct MovieView: View {
    
    @Binding var movie: Movie
    
    var body: some View {
        VStack{
            Text(movie.title)
            Text(movie.summary)
            Toggle("Watched", isOn: $movie.watched)
        }
    }
}

请注意,我将您的 ForEach 更改为使用 id - 如果您有两部电影共享同一个标题,使用 title 可能会很有趣。如果您不想使用 ID,则必须修改 bindingForId 函数以根据标题进行过滤。

我还包括足够的样板代码来获取它 运行 作为我的示例(添加 NavigationView、使用 @ObservedObject、添加示例数据等),但是 none 其中应该对原始问题的方法有影响。不过,一件重要的事情是要注意 Moviestruct.