SwiftData with NavigationStack

84 Views Asked by At

I'm trying to learn the basics of Swift and am currently struggling with crashes when using SwiftData with NavigationStack. I want my navigation to work as outlines in the wireframe of navigation. Basically I just need to go from a list of items in ContentView to a detail view (ItemDetailView) of items OR to an add item view (EditItemView). Later on, I also need to go from my ItemDetailView to EditItemView but I haven't gotten around to trying to put that in yet. Anything input into the EditItemView should be saved/managed with SwiftData.

I have all the pieces working individually, but I'm not getting the desired behavior when I try to put them all together. I am specifically getting stuck with the following:

  • It crashes when I add a new item AFTER Navigating to the ItemDetailview for any Item. I can successfully add Items if I navigate to the EditItemView first though without clicking on any Item in the list and going to it's ItemDetailView.

  • From ContentView, when I first click on any Item in the list the sheet pops up but then immediately disappears. The second time I click on it the proper view appears.

My Views are below:

import SwiftUI
import SwiftData

struct ContentView: View {
    @Environment(\.modelContext) private var modelContext
    @Query private var items: [Item]
    @State private var isPresentingNewItemView = false
    @State private var path = [Item]()

    var body: some View {
        NavigationStack(path: $path) {
            List {
                ForEach(items) { item in
                    NavigationLink {
                        ItemDetailView(item: item)
                    } label: {
                        Text(item.name)
                    }
                }
            }
            .toolbar {
                ToolbarItem {
                    Button(action: addItem) {
                        Label("Add Item", systemImage: "plus")
                    }
                }
            }
        }
        .sheet(isPresented: $isPresentingNewItemView) {
            EditItemView(item: Item.emptyItem, isPresentingNewItemView: $isPresentingNewItemView)
        }
    }

    private func addItem() {
        withAnimation {
            isPresentingNewItemView = true
        }
    }
}

#Preview {
    do{
        let config = ModelConfiguration(isStoredInMemoryOnly: true)
        let container = try! ModelContainer(for: Item.self, configurations: config)
        
        for i in 1..<10 {
            let item = Item(name: "Example Item \(i)")
            container.mainContext.insert(item)
        }
        
        return ContentView()
            .modelContainer(container)
    } catch {
        fatalError("Failed to create model container.")
    }
}
import SwiftUI
import SwiftData

struct EditItemView: View {
    @Environment(\.modelContext) private var modelContext
    @Bindable var item: Item
    @Binding var isPresentingNewItemView: Bool
    @State private var updatedItem: Item = Item.emptyItem
    
    var body: some View {
        NavigationStack {
            Form {
                TextField("Name", text: $updatedItem.name)
            }
            .toolbar{
                Button("Save", action: saveItem)
            }
        }
    }
    private func saveItem() {
        modelContext.insert(updatedItem)
        isPresentingNewItemView = false
    }
}

#Preview {
    do {
        let config = ModelConfiguration(isStoredInMemoryOnly: true)
        let container = try ModelContainer (for: Item.self, configurations: config)
        
        let example = Item(name: "TEST ITEM")
        return EditItemView(item: example, isPresentingNewItemView: .constant(true))
            .modelContainer(container)
    } catch {
        fatalError("Failed to create model container.")
    }
}
import SwiftUI

struct ItemDetailView: View {
    @Bindable var item: Item
    
    var body: some View {
        NavigationStack {
            Form {
                Text("Name: \(item.name)")
            }
            .toolbar {
                Button("Edit", action: editCard)
            }
        }
    }
    func editCard() {
        
    }
}

#Preview {
    do {
        let example = Item(name: "TEST ITEM")
        return ItemDetailView(item: example)
    }
}
import Foundation
import SwiftData

@Model
final class Item {
    var name: String
    
    init(name: String = "") {
        self.name = name
    }
}

extension Item {
    static var emptyItem: Item {
        Item(
            name: ""
        )
    }
}

I've tried paring everything down to just the basics, but as a new programmer I still can't figure this out. Any help or direction is greatly appreciated!

0

There are 0 best solutions below