0

I am trying to save what's in in a textfield to Core Data, but nothing happen. Nothing is saved to core data and dont know why, because is worked before.

struct LivrareView: View {
    @StateObject var coreDataViewModel = CoreDataViewModel()
    
   
    var body: some View {
        
   let delivery = Binding(
            get: {coreDataViewModel.savedDetails.first?.wrappedDeliveryAddress ?? ""},
            set: {coreDataViewModel.savedDetails.first?.wrappedDeliveryAddress = $0})

VStack(alignment: .leading) {
                Text(Texts.livrareViewText1)
                    .foregroundColor(.orange)
         
                TextField("Ex", text: delivery , onEditingChanged: { _ in
                    coreDataViewModel.saveContext()
                })
}
}
}

    func saveContext() {
        DispatchQueue.main.async {
            do {
                try context.save()
                print("Savedd succesfully")
            } catch let error {
                print("Error is \(error.localizedDescription)")
            }
            self.fetchSavedMenu()
            self.fetchSavedDetails()
        }
        }

CoreData View Model :

class CoreDataViewModel : ObservableObject {
    let manager = CoreDataManager.instance
    @Published var savedMenu: [LocalMenu] = []
    @Published var savedDetails : [LocalDetails] = []
    
    var countDictionary: [Int16:Int] {
        savedMenu.reduce(into: [:]) {
          $0[$1.id, default: 0] += 1
      }
    }
    
    var savedCartToShow: [LocalMenu] {
        var alreadyThere = Set<Int16>()
        return savedMenu.compactMap { cart -> LocalMenu? in
            guard !alreadyThere.contains(cart.id) else { return nil }
            alreadyThere.insert(cart.id)
            return cart
        }
    }
    
    init() {
        
        fetchSavedMenu()
        fetchSavedDetails()
    }
    func fetchSavedMenu() {
        
        let request = NSFetchRequest<LocalMenu>(entityName: "LocalMenu")
        do {
            savedMenu = try manager.context.fetch(request)
        } catch let error {
            print("Error fetching \(error)")
        }
    }
    
    func fetchSavedDetails() {
        let request = NSFetchRequest<LocalDetails>(entityName: "LocalDetails")
        do {
            savedDetails = try manager.context.fetch(request)
        } catch let error {
            print("Error fetching \(error)")
        }
    }
    
    func saveContext() {
        DispatchQueue.main.async {
            do {
                try context.save()
                print("Savedd succesfully")
            } catch let error {
                print("Error is \(error.localizedDescription)")
            }
            self.fetchSavedMenu()
            self.fetchSavedDetails()
        }
        }
    public func addTask(name: String, grams: Double, price: Float, id: Int) {
       
       let newCart = LocalMenu(context: manager.context)
        newCart.name = name
        newCart.grams = grams
        newCart.price = Int16(price)
        newCart.id = Int16(id)
        saveContext()
    }
}

EDITED : Added CoreDataViewModel. I have 2 entities : LocalDetails and LocalMenus. I have a list of "Food Menus" with a + sign on the right side of them, and when the buttons is pressed " addTask() " func is called.

LocalDetails is used to store data for the users, but maybe the error is that is not created an object for the LocalDetails ?

CoreData Manager :

class CoreDataManager {
    
    static let instance = CoreDataManager() 
    
    let container : NSPersistentContainer
    let context : NSManagedObjectContext
    init() {
        container = NSPersistentContainer(name: "OneBiteContainer")
        container.loadPersistentStores { description, error in
            if let error = error {
                print("error loading core ddata \(error)")
            }
        }
        print(container.persistentStoreDescriptions.first?.url)
        context = container.viewContext
    }
    func save()
    {
        do {
            try context.save()
            print("Savedd succesfully")
        } catch let error {
            print("Error is \(error.localizedDescription)")
        }
    }
}
4265756b69
  • 37
  • 6
  • 1
    Welcome to Stack Overflow! Please take the [tour](https://stackoverflow.com/tour) and see: [How do I ask a good question?](https://stackoverflow.com/help/how-to-ask) and [How to create a Minimal, Reproducible Example](https://stackoverflow.com/help/minimal-reproducible-example). We are really going to need you `CoreDataViewModel` and your `PersistenceController` to debug this, though I am not sure where you have shown a managed object that you are attempting to save. – Yrb Dec 16 '21 at 00:23
  • @Yrb Added now, check. – 4265756b69 Dec 16 '21 at 00:29
  • Does this answer your question? [ForEach not properly updating with dynamic content SwiftUI](https://stackoverflow.com/questions/70016175/foreach-not-properly-updating-with-dynamic-content-swiftui) – lorem ipsum Dec 16 '21 at 00:32
  • There is so much going on in your code that the error could be anywhere. I would start but getting rid of that Binding and wrapping the CoreData object directly with `@ObservedObject`. `wrappedDeliveryAddress` also looks suspicious. – lorem ipsum Dec 16 '21 at 00:35
  • If i am getting rid of that Binding, i can't use ".first " to bind in TextField – 4265756b69 Dec 16 '21 at 00:42
  • The save function itself looks fine, but this code has a lot of issues. You would be much better off implementing a `@FetchRequest` in your `LivrareView` and work directly with your Core Data model rather than going through the `CoreDataViewModel`. The necessary functions for the entities can be in an extension of the entity's class. My most likely guess as to why nothing has saved is that you haven't updated any attribute on an entity. Last thing, you should not be trying to save in an `onEditingChanged` block. You are literally trying to save every keystroke. Use `.onCommit()` if you need to – Yrb Dec 16 '21 at 00:48
  • Like I said there are too many points of potential error. Every ? Should be checked for a nil that value might be empty. Fetching is unordered so first could be any item of the array it will change every time. – lorem ipsum Dec 16 '21 at 01:11

1 Answers1

0

If you change the ? to an ! you will get a crash and you will instantly know that your issue is the custom Binding because by leaving the ? you are ignoring that there is an issue.

As a general rule every ? and ! should be preceded by an if, if let or guard.

Now to help you get it working...

All CoreData objects are ObservableObjects so if you want to see changes/edit them you have to wrap them in an @ObservedObject to do that you have to put things like TextFields in a subview like this.

struct DetailsView: View{
    @ObservedObject var vm: CoreDataViewModel
    @ObservedObject var details: LocalDetails
    
    var body: some View{
        TextField("Ex", text: $details.deliveryAddress.bound , onEditingChanged: { _ in
            vm.saveContext()
        })
    }
}

And you would use this in your LivrareView like this.

struct LivrareView: View {
    @StateObject var coreDataViewModel = CoreDataViewModel()
    var body: some View {
        
        VStack(alignment: .leading) {
            Text("Texts.livrareViewText1")
                .foregroundColor(.orange)
            //Checking for `nil`
            if coreDataViewModel.savedDetails.first != nil {
                DetailsView(vm: coreDataViewModel, details: coreDataViewModel.savedDetails.first!)
            }else{ //And reacting somehow to finding it
                Button("add details", action: {
                    coreDataViewModel.addDetail(address: "")
                })
            }
            
        }
    }
}

Now you have chosen to not observe the store by using NSFetchRequest instead of @FetchRequest or NSFetchedResultsController so you have to somehow trigger a refresh when then store changes.

One way is when you add an object you can call your fetching methods.

class CoreDataViewModel : ObservableObject {
    let manager = CoreDataManager.instance
    @Published var savedMenu: [LocalMenu] = []
    @Published var savedDetails : [LocalDetails] = []
    
    var countDictionary: [Int16:Int] {
        savedMenu.reduce(into: [:]) {
            $0[$1.id, default: 0] += 1
        }
    }
    
    var savedCartToShow: [LocalMenu] {
        var alreadyThere = Set<Int16>()
        return savedMenu.compactMap { cart -> LocalMenu? in
            guard !alreadyThere.contains(cart.id) else { return nil }
            alreadyThere.insert(cart.id)
            return cart
        }
    }
    
    init() {
        
        fetchSavedMenu()
        fetchSavedDetails()
    }
    func fetchSavedMenu() {
        
        let request = NSFetchRequest<LocalMenu>(entityName: "LocalMenu")
        do {
            savedMenu = try manager.context.fetch(request)
        } catch let error {
            print("Error fetching \(error)")
        }
    }
    
    func fetchSavedDetails() {
        let request = NSFetchRequest<LocalDetails>(entityName: "LocalDetails")
        do {
            savedDetails = try manager.context.fetch(request)
        } catch let error {
            print("Error fetching \(error)")
        }
    }
    
    func saveContext() {
        //Simplify this call the manager vs recreate code
        manager.save()
    }
    public func addMenu(name: String, grams: Double, price: Float, id: Int) {
        
        let newCart = LocalMenu(context: manager.context)
        newCart.name = name
        newCart.grams = grams
        newCart.price = Int16(price)
        newCart.id = Int16(id)
        saveContext()
        
        fetchSavedMenu()
    }
    
    public func addDetail(address: String) {
        
        let newCart = LocalDetails(context: manager.context)
        newCart.deliveryAddress = address
        saveContext()
        
        fetchSavedDetails()
    }
}

Also, you'll see that I use .bound to handle the optional. The code comes from another SO post. It works with any Optional String so you do have to create the makeshift wrapped variables. I wish I had the original link for it but I can't find it. It is great.

extension Optional where Wrapped == String {
    var _bound: String? {
        get {
            return self
        }
        set {
            self = newValue
        }
    }
    var bound: String {
        get {
            return _bound ?? ""
        }
        set {
            _bound = newValue
        }
    }

}
lorem ipsum
  • 21,175
  • 5
  • 24
  • 48