You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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的普通属性,未关联到已持久化对象,所以不会触发自动追踪。

解决方案

  1. 拆分输入模板与数据实例:不要直接用@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
    }
}
  1. 修正预览模型配置:当前预览仅指定Item.self作为模型容器,需包含Movie和Director:
#Preview {
    ContentView()
        .modelContainer(for: [Movie.self, Director.self], inMemory: true)
}

内容的提问来源于stack exchange,提问作者Felipe Cruxen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 11:00:14