Home > database >  SwiftUI: Saving likes in CoreData for each individual cell
SwiftUI: Saving likes in CoreData for each individual cell

Time:02-10

How do I save the like state for each individual cell? I decided to save via CoreData, but the like is saved for all cells at once.

In the Core Data model (LikedDB) there is an attribute such as isLiked

In the class there is a variable isLiked, which changes the state of the like:

class ModelsViewModel: ObservableObject{

    @Published var isLiked = false

    func like() {
        isLiked.toggle()
    }
}

This is how I save the like state from ModelsViewModel And in label I use

struct CellView: View{
    
   //For CoreData
    @Environment(\.managedObjectContext) var managedObjectContext
    @FetchRequest(entity: LikedDBE.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \LikedDBE.name, ascending: true)]) var manyLikedDB: FetchedResults<LikedDBE>

    //For like
    @ObservedObject var cellViewModel: ModelsViewModel = ModelsViewModel()

    var body: some View{

        Button(action: {
        let likedDBE = LikedDBE(context: self.managedObjectContext)
        likedDBE.isLiked = cellViewModel.isLiked //I indicate that isLiked from CoreData = isLiked from ModelsViewModel()

        do{
            cellViewModel.like() //func from ModelsViewModel()
            try self.managedObjectContext.save() //Save
        } catch{
            print(error)
        }

        }, label: {
        Image(systemName: cellViewModel.isLiked ? "heart.fill" : "heart") //Here I use
           .frame(width: 22, height: 22)
           .foregroundColor(cellViewModel.isLiked ? .red : .black) //And here I use
})

And if I use cellViewModel.isLiked, then when I click like, the like is displayed only on the one I clicked on, but the state is not saved when restarting the application, if I use likedDB.isLiked, then the like is displayed on all cells at once, but the like is saved after restarting.

I want the like to be only on the cell I clicked on and it will be saved after restarting the application.

CodePudding user response:

Short answer you need something like this.

Button("add", action: {
    //Create a new object
    let new: LikedDBE = store.create()
    //Trigger a child view that observes the object
    selection = new.objectID
})

It is a button that creates the object and triggers a child view so you can observe and edit it.

Long answer will be below just copy and paste all the code into your ContentView file there are comment throughout.

import SwiftUI
import CoreData

struct ContentView: View {
    @StateObject var store: CoreDataPersistence = .init()
    var body: some View{
        LikesListView()
        //This context is aware of you are in canvas/preview
            .environment(\.managedObjectContext, store.context)
    }
}
struct LikesListView: View {
    @EnvironmentObject var store: CoreDataPersistence
    
    @FetchRequest( sortDescriptors: [NSSortDescriptor(keyPath: \LikedDBE.name, ascending: true)]) var manyLikedDB: FetchedResults<LikedDBE>
    //Control what NavigationLink is opened
    @State var selection: NSManagedObjectID? = nil
    
    var body: some View {
        NavigationView{
            List{
                ForEach(manyLikedDB){ object in
                    NavigationLink(object.name ?? "no name", tag: object.objectID, selection: $selection, destination: {LikeEditView(obj: object)})
                }
            }.toolbar(content: {
                Button("add", action: {
                    //Create a new object
                    let new: LikedDBE = store.create()
                    //Trigger a child view that observes the object
                    selection = new.objectID
                })
            })
        }.environmentObject(store)
    }
}
struct LikeEditView: View{
    @EnvironmentObject var store: CoreDataPersistence
    @Environment(\.dismiss) var dismiss
    //Observe the CoreData object so you can see changes and make them
    @ObservedObject var obj: LikedDBE
    var body: some View{
        TextField("name", text: $obj.name.bound).textFieldStyle(.roundedBorder)
        Toggle(isOn: $obj.isLiked, label: {
            Text("is liked")
        })
            .toolbar(content: {
                ToolbarItem(placement: .navigationBarLeading){
                    Button("cancel", role: .destructive, action: {
                        store.resetStore()
                        dismiss()
                    })
                }
                ToolbarItem(placement: .navigationBarTrailing){
                    
                    Button("save", action: {
                        store.update(obj)
                        dismiss()
                    })
                }
            })
            .navigationBarBackButtonHidden(true)
    }
}
struct LikesListView_Previews: PreviewProvider {
    static var previews: some View {
        LikesListView()
    }
}

///Generic CoreData Helper not needed jsuto make stuff easy.
class CoreDataPersistence: ObservableObject{
    //Use preview context in canvas/preview
    //The context is for both Entities,
    let context = ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" ? PersistenceController.preview.container.viewContext : PersistenceController.shared.container.viewContext
    ///Non observing array of objects
    func getAllObjects<T: NSManagedObject>() -> [T]{
        let listRequest = T.fetchRequest()
        do {
            return try context.fetch(listRequest).typeArray()
        } catch let error {
            print ("Error fetching. \(error)")
            return []
        }
    }
    ///Creates an NSManagedObject of any type
    func create<T: NSManagedObject>() -> T{
        T(context: context)
        //Can set any defaults in awakeFromInsert() in an extension for the Entity
        //or override this method using the specific type
    }
    ///Updates an NSManagedObject of any type
    func update<T: NSManagedObject>(_ obj: T){
        //Make any changes like a last modified variable
        save()
    }
    ///Creates a sample
    func addSample<T: NSManagedObject>() -> T{
        let new: T = create()
        //Can add sample data here by type checking or overriding this method
        return new
    }
    ///Deletes  an NSManagedObject of any type
    func delete(_ obj: NSManagedObject){
        context.delete(obj)
        save()
    }
    func resetStore(){
        context.rollback()
        save()
    }
    private func save(){
        do{
            try context.save()
        }catch{
            print(error)
        }
    }
}
extension Optional where Wrapped == String {
    var _bound: String? {
        get {
            return self
        }
        set {
            self = newValue
        }
    }
    var bound: String {
        get {
            return _bound ?? ""
        }
        set {
            _bound = newValue
        }
    }

}
  • Related