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

Swiftui 如何从列表元素更新全局状态?,swiftui,swiftui-list,Swiftui,Swiftui List,我正在构建一个电影跟踪应用程序,我想从电影的详细信息页面更改电影的观看状态。有一个电影列表。按到元素时,我将电影对象传递到细节视图,并尝试在其中更改观看状态 MovieListView.swift文件: struct MovieListView: View { @EnvironmentObject var movies: Movies var body: some View { List(movies.allMovies, id: \.title) { movi

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

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.wasted
给出以下错误:
无法在范围中找到“$movie”


我尝试了绑定,但由于传递给MovieView的电影对象不是状态,因此绑定无法工作。

您需要提供一个
@binding
。对于项目列表,这意味着您需要某种方法来生成
@Binding
,以便子视图可以更新原始列表。您可以在
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: { $0.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)
        }
    }
}

类电影:可观察对象{
@已发布var allMovies:[电影]=[电影(观看:错误,标题:“电影1”,摘要:“摘要1”),电影(观看:错误,标题:“电影2”,摘要:“摘要2”)]
func bindingForId(id:UUID)->Binding{
.init{()->中的电影
self.allMovies.first(其中:{$0.id==id})??电影(观看:false,标题:,摘要:)
}集合:{(newValue)在
self.allMovies=self.allMovies.map{movie in
如果movie.id==newValue.id{return newValue}
回归电影
}
}
}
}
结构电影{
var id=UUID()
瓦尔:布尔
变量标题:字符串
变量摘要:字符串
}
结构ContentView:View{
@ObservedObject var movies:movies=movies()
var body:一些观点{
导航视图{
列表(movies.allMovies,id:\.id){movie in
导航链接(
目标:MovieView(电影:movies.bindingForId(id:movie.id))){
文本(电影名称)
}
}.navigationTitle(“电影列表”)
}
}
}
结构电影视图:视图{
@电影:电影
var body:一些观点{
VStack{
文本(电影名称)
文本(电影摘要)
切换(“已观看”,isOn:$movie.waved)
}
}
}
请注意,我将您的
ForEach
更改为使用
id
——如果您有两部同名电影,那么使用
标题可能会很有趣。如果不想使用ID,则必须修改
bindingForId
函数以根据标题进行筛选


我还包括了足够的样板代码,以使其作为示例运行(添加
导航视图
、使用
@ObservedObject
、添加示例数据等),但这些都不应该影响到解决原始问题的方法。不过有一点很重要,那就是要注意,
Movie
是一个
struct

使用索引不是更好吗?不是每次都要寻找单个项目并进行搜索吗?不是真的--
索引
非常脆弱。例如,如果订单发生变化怎么办?例如,在一个应用程序中,数据是从在线源同步的,在详细信息视图中很容易更改。即使不是以枚举方式?
enumerated
只使用索引。我并不是说你做不到--我只是说它天生比使用ID更脆弱。我知道你的意思,但是绑定会使用多次,即使不是真正的更新,我认为它会让系统屈服。