Search code examples
swiftuiswiftdata

Issue with Displaying Newly Added Items in SwiftUI List


I'm working on a SwiftUI app where I have a Todo model and a TodoView to display a list of todos. The todos are stored using SwiftData, and I'm using a @Query property to fetch and display them in a List. However, when I add a new todo using a button, the new item doesn't appear in the list, even though the note variable contains the correct value.

import SwiftUI
import SwiftData

@main
struct mmApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .modelContainer(for: Todo.self)
    }
}
import SwiftUI
import SwiftData

struct todo: View {
    @Query private var todos: [Todo]
    @Environment(\.modelContext) private var context
    @State private var note = ""

    var body: some View {
        ZStack {
            VStack {
                NavigationStack {
                    List(todos, id: \.self) { todo in
                        HStack {
                            Text("Hello")
                                .swipeActions {
                                    Button("Delete", role: .destructive) {
                                        context.delete(todo)
                                    }
                                }
                        }
                    }
                    .safeAreaInset(edge: .bottom) {
                        VStack(alignment: .center, spacing: 20) {
                            Text("New ToDo")
                                .font(.headline)
                            HStack {
                                TextField("Todo", text: $note)
                                    .padding()
                                    .background(Color.white)
                                    .cornerRadius(8)
                                    .shadow(radius: 8)
                                    .frame(width: 300)
                                
                                Button(action: {
                                    let newNote = Todo(note: note)
                                    context.insert(newNote)
                                    print(newNote.id, newNote.note)
                                    note = ""
                                }) {
                                    Label("Add", systemImage: "plus")
                                        .foregroundStyle(.white)
                                        .font(.subheadline)
                                        .bold(true)
                                }
                                .frame(width: 60, height: 44)
                                .background(Color.green)
                                .cornerRadius(10)
                            }
                        }
                        .padding()
                        .background(.bar)
                    }
                }
            }
        }
    }
}

#Preview {
    todo()
}
swift
import Foundation
import SwiftData

@Model
class Todo {
    var id: UUID
    var note: String
    
    init(note: String) {
        self.id = UUID()
        self.note = note
    }
}

Solution

  • You need to provide a model container to the Preview:

    #Preview {
        ToDoView()
            .modelContainer(for: ToDo.self)
    }
    

    But your code has many other issues, beginning with not respecting Swift naming conventions (like having a struct named todo), and using deprecated modifiers like .cornerRadius.

    There's also no need for all the forced frame values for the button and text field to achieve a proper layout.

    Here's the code with some adjustments:

    import SwiftUI
    import SwiftData
    
    //Main App
    @main
    struct ToDoApp: App {
        var sharedModelContainer: ModelContainer = {
            let schema = Schema([
                ToDo.self,
            ])
            let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
            
            do {
                return try ModelContainer(for: schema, configurations: [modelConfiguration])
            } catch {
                fatalError("Could not create ModelContainer: \(error)")
            }
        }()
        
        var body: some Scene {
            WindowGroup {
                ToDoView()
            }
            .modelContainer(sharedModelContainer)
        }
    }
    
    //Root view
    struct ToDoView: View {
        @Query private var toDos: [ToDo]
        @Environment(\.modelContext) private var context
        @State private var note = ""
        
        var body: some View {
            ZStack {
                VStack {
                    NavigationStack {
                        List(toDos, id: \.self) { toDo in
                            HStack {
                                Text(toDo.note)
                                    .swipeActions {
                                        Button("Delete", role: .destructive) {
                                            context.delete(toDo)
                                        }
                                    }
                            }
                        }
                        .safeAreaInset(edge: .bottom) {
                            VStack(alignment: .center, spacing: 20) {
                                Text("New To Do")
                                    .font(.headline)
                                
                                HStack(spacing: 12) {
                                    TextField("To do: ...", text: $note)
                                        .padding()
                                        .background(Color.white, in: RoundedRectangle(cornerRadius: 8))
                                        .shadow(radius: 8)
                                    // .frame(width: 300)
                                    
                                    Button(action: {
                                        let newNote = ToDo(note: note)
                                        context.insert(newNote)
                                        print(newNote.id, newNote.note)
                                        note = ""
                                    }) {
                                        Label("Add", systemImage: "plus")
                                            .foregroundStyle(.white)
                                            .font(.subheadline)
                                            .bold(true)
                                            .padding()
                                    }
                                    .fixedSize()
                                    .background(Color.green, in: RoundedRectangle(cornerRadius: 10))
                                }
                            }
                            .padding()
                            .background(.bar)
                        }
                    }
                }
            }
        }
    }
    
    #Preview {
        ToDoView()
            .modelContainer(for: ToDo.self)
    }
    
    @Model
    class ToDo {
        var id: UUID
        var note: String
        
        init(note: String) {
            self.id = UUID()
            self.note = note
        }
    }