SwiftData异常:选择Picker中导演时Movie自动加入列表
问题描述
关闭SwiftData自动保存后出现异常:在Picker中选择导演时,未调用添加函数的Movie会被自动加入电影列表,但该Movie并未持久化到SQLite,重启应用后条目消失;编辑文本框时无此问题。
测试代码
// // ContentView.swift // DataTest // // Created by Felipe Cruxen on 06/07/24. // import SwiftUI import SwiftData @Model class Director { var id: UUID = UUID() var name: String init(name: String) { self.name = name } } @Model class Movie { var id: UUID = UUID() var name: String var director: Director? init(name: String, director: Director? = nil) { self.name = name self.director = director } } struct ContentView: View { @Environment(\.modelContext) private var modelContext @Query private var items: [Item] @Query private var movies: [Movie] @Query private var directors: [Director] @State private var movie: Movie = Movie(name: "") @State private var director: Director = Director(name: "") var body: some View { NavigationSplitView { List { ForEach(movies) { movie in NavigationLink { Text("Movies at \(movie.name)") } label: { Text(movie.name) Button("-") { deleteMovie(movie: movie) } } } } .navigationSplitViewColumnWidth(min: 180, ideal: 200) .toolbar { ToolbarItem { Button(action: addDirector) { Label("Add Item", systemImage: "plus") } } } List { ForEach(directors) { director in NavigationLink { Text("Directors at \(director.name)") } label: { Text(director.name) Button("-") { deleteDirector(director: director) } } } } } detail: { TextField("Movie Name", text: $movie.name) Picker("Director", selection: $movie.director) { ForEach(directors, id: \.self){ director in Text("\(director.name)").tag(director as Director?) } } Button(action: addDMovie) { Label("Add Movie", systemImage: "plus") } TextField("Director Name", text: $director.name) Button(action: addDirector) { Label("Add Director", systemImage: "plus") } } } private func addDirector() { withAnimation { modelContext.insert(director) try? modelContext.save() director = Director(name: "") } } private func addDMovie() { withAnimation { let toSave = movie modelContext.insert(toSave) movie = Movie(name: "") } } private func deleteDirector(director: Director) { withAnimation { modelContext.delete(director) try? modelContext.save() } } private func deleteMovie(movie: Movie) { withAnimation { modelContext.delete(movie) try? modelContext.save() } } } #Preview { ContentView() .modelContainer(for: Item.self, inMemory: true) }
现象截图
- 选择导演前:

- 选择导演后:

问题原因与解决方案
原因分析
当在Picker中选择导演时,SwiftData会自动将@State中的Movie实例关联到已持久化的Director对象。由于关联关系存在,SwiftData上下文会把这个未插入的Movie标记为追踪对象,进而被@Query查询到并显示在列表中。但因为没有调用modelContext.insert()和save(),该实例并未被持久化,重启后自然消失。而编辑文本框仅修改Movie的普通属性,未关联到已持久化对象,所以不会触发自动追踪。
解决方案
- 拆分输入模板与数据实例:不要直接用
@State存储Movie对象作为输入模板,改用普通@State变量存储输入内容,添加时再创建Movie实例:
// 替换原@State private var movie: Movie = Movie(name: "") @State private var newMovieName: String = "" @State private var selectedDirector: Director? = nil
修改详情视图的输入部分:
TextField("Movie Name", text: $newMovieName) Picker("Director", selection: $selectedDirector) { ForEach(directors, id: \.self){ director in Text("\(director.name)").tag(director as Director?) } } Button(action: addDMovie) { Label("Add Movie", systemImage: "plus") }
更新addDMovie函数:
private func addDMovie() { withAnimation { let newMovie = Movie(name: newMovieName, director: selectedDirector) modelContext.insert(newMovie) try? modelContext.save() newMovieName = "" selectedDirector = nil } }
- 修正预览模型配置:当前预览仅指定
Item.self作为模型容器,需包含Movie和Director:
#Preview { ContentView() .modelContainer(for: [Movie.self, Director.self], inMemory: true) }
内容的提问来源于stack exchange,提问作者Felipe Cruxen
相关产品推荐
相关产品推荐

