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!