[Basics] Saving in SwiftData doesn't work

GM, I'm kinda new in SwiftData, I'm trying to build an app with Steppers and I need to acess the value of steppers in other views and save them. I started with this code but it doen't save the results, can someone please help? Thanks alot.

import SwiftData

@main
struct TesteStepApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .modelContainer(for: GroceryListItem.self)
    }
}

@Model
class GroceryListItem {
    let stepperValue: Int
    
    init(stepperValue: Int = 0) {
        self.stepperValue = stepperValue
    }
}

struct ContentView: View {
    @Environment(\.modelContext) var context
    @State private var stepperValue: Int = 0

    var body: some View {
        VStack {
            Stepper("Value: \(stepperValue)", value: $stepperValue, in: 0...100)
                .padding()
        }
        .padding()
        .onChange(of: stepperValue) { oldValue, newValue in
            insertValue(newValue)
        }
    }

    private func insertValue(_ value: Int) {
    }
}

#Preview {
    ContentView()
}
Answered by mcomisso in 794402022

The stepper should act on the model that is stored in memory. Here you are inserting a new value, but it should increase the stepperValue in an entity of GroceryListItem.

First you need to create an entity, and save it.

let item = GroceryListItem()
context.insert(item)

Then you need to load that item, and use it in a binding to directly bind the stepper to its value.

This could become your list of elements:

List {
  ForEach(items) { item in

    @Bindable var bindingItem = item

    NavigationLink {

      // Destination view, a simple Stepper.
      Stepper(value: $bindingItem.stepperValue) {
        Text("\(item.stepperValue)")
      }

    } label: {
      Text("Item with value: \(item.stepperValue)")
    }         
  }
}

onChange is not needed for this. :)

Accepted Answer

The stepper should act on the model that is stored in memory. Here you are inserting a new value, but it should increase the stepperValue in an entity of GroceryListItem.

First you need to create an entity, and save it.

let item = GroceryListItem()
context.insert(item)

Then you need to load that item, and use it in a binding to directly bind the stepper to its value.

This could become your list of elements:

List {
  ForEach(items) { item in

    @Bindable var bindingItem = item

    NavigationLink {

      // Destination view, a simple Stepper.
      Stepper(value: $bindingItem.stepperValue) {
        Text("\(item.stepperValue)")
      }

    } label: {
      Text("Item with value: \(item.stepperValue)")
    }         
  }
}

onChange is not needed for this. :)

Thank you for your help, but how can I create that entity? :/

is it inside the @Model?

I tried with addItem but it didn't work :(

can you please show me the full code? i would be grateful :)

anyone pls? :)

Don't know if you already got your answer elsewhere but I included a working sample app below.

Like mcomisso already said, to create a new entity, you have to insert it into the database using the ModelContext.

let newItem = GroceryListItem(name: "New Item", stepperValue: 1)
context.insert(newItem)

Where you do this depends on your app.

GroceryListApp.swift

import SwiftUI
import SwiftData

@main
struct GroceryListApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .modelContainer(for: GroceryListItem.self)
    }
}

GroceryListItem.swift

import SwiftData

@Model
final class GroceryListItem {
    var name: String
    var stepperValue: Int
    
    init(name: String, stepperValue: Int = 1) {
        self.name = name
        self.stepperValue = stepperValue
    }
}

ContentView.swift

import SwiftUI
import SwiftData

struct ContentView: View {
    @Environment(\.modelContext) var context
    @Query var groceryListItems: [GroceryListItem]
    
    @State private var newItemName: String = ""

    var body: some View {
        List {
            Section {
                TextField("New Item", text: $newItemName)
                Button("Add new item", action: insertNewItem)
            }
            
            Section {
                ForEach(groceryListItems) { groceryListItem in
                    GroceryListItemView(groceryListItem: groceryListItem)
                }
                .onDelete(perform: deleteItem)
            }
        }
    }
    
    func insertNewItem() {
        let newItem = GroceryListItem(name: newItemName)
        context.insert(newItem)
        newItemName = ""
    }
    
    func deleteItem(at indexSet: IndexSet) {
        for index in indexSet {
            context.delete(groceryListItems[index])
        }
    }
}

#Preview {
    ContentView()
        .modelContainer(for: GroceryListItem.self, inMemory: true)
}

GroceryListItemView.swift

import SwiftUI

struct GroceryListItemView: View {
    @Bindable var groceryListItem: GroceryListItem
        
    var body: some View {
        Stepper("\(groceryListItem.name) (\(groceryListItem.stepperValue)x)", value: $groceryListItem.stepperValue, in: 1...100)
    }
}

#Preview {
    // Note that @Previewable was introduced in the XCode 16 Beta
    @Previewable @State var previewItem = GroceryListItem(name: "Milk")
    List {
        GroceryListItemView(groceryListItem: previewItem)
    }
}

(I like to keep my view body clear of all logic so I extracted the @Binding and the list item into its own view.)

Screenshot

Thank you so much!

You helped me a lot. I really appreciate it.

Best wishes.

[Basics] Saving in SwiftData doesn't work
 
 
Q