UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: CoreData and CloudKit not reflecting updates to items across devices

Forums > SwiftUI

I recently tried converting my app from just using CoreData to also use CloudKit so that a user would be able to use another device under the same account to work with items.

I followed this video: https://youtu.be/TsfOYHbf4Ew to do this.

While added and deleted items are reflected, when I update some part of the entitiy, for instance a Bool attribute is changes from false to true, it is not reflected in the CloudKit container, and therefore the other device.

I've tried to utilize other resources to rectify this, without success.

.....and now I'm lost... I don't really understand how or why this is happening.

I'm trying to not make this the longest post ever so my examples are just portions of the code...

here is part of the class that does all my container setup and updating:

class CoreDataViewModel: ObservableObject {

    private static var container: NSPersistentCloudKitContainer = {
        let container = NSPersistentCloudKitContainer(name: "CoreDataContainer")

        guard let persistentStoreDescription = container.persistentStoreDescriptions.first else {
                    fatalError("\(#function): Failed to retrieve a persistent store description.")
                }
                persistentStoreDescription
                    .setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
                persistentStoreDescription
                    .setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)

        container.loadPersistentStores(completionHandler: { (description, error) in
                if let error = error {
                    print("ERROR LOADING CORE DATA. \(error)")
                }
            })

        return container

    }()

    init() {

        CoreDataViewModel.container.viewContext.automaticallyMergesChangesFromParent = true
        CoreDataViewModel.container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy

        try? CoreDataViewModel.container.viewContext.setQueryGenerationFrom(.current)

        fetchTools()
        NotificationManager.instance.requestNotificationPermissions()
    }

    @Published var savedEntities: [ToolEntity] = []

    func fetchTools() {
        let request = NSFetchRequest<ToolEntity>(entityName: "ToolEntity")
        let sort = NSSortDescriptor(key: #keyPath(ToolEntity.loandue), ascending: true)
        request.sortDescriptors = [sort]
        do {
            savedEntities = try CoreDataViewModel.container.viewContext.fetch(request)
        } catch let error {
            print("Error fetching \(error)")
        }

    }

    func addTool(nametext: String, sntext: String, notestext: String, pickedImage: Data) {
        let newTool = ToolEntity(context: CoreDataViewModel.container.viewContext)
        newTool.name = nametext
        if sntext.isEmpty {
            newTool.sn = "no serial"
        } else {
            newTool.sn = sntext
        }
        if notestext.isEmpty {
            newTool.notes = "no notes"
        } else {
            newTool.notes = notestext
        }
        newTool.status = "In the box"
        newTool.incart = false
        newTool.loanedout = false
        newTool.edit = false
        newTool.lost = false
        newTool.lsColorRed = 0
        newTool.lsColorGreen = 1.0
        newTool.lsColorBlue = 0
        let imageID = UUID().uuidString
        newTool.id = imageID
        if !pickedImage.isEmpty { newTool.image = pickedImage }
        else { newTool.image = DefaultPhoto }
        saveData()
    }

    func deleteTool(indexSet: IndexSet) {
        guard let index = indexSet.first else { return }
        let entity = savedEntities[index]
        CoreDataViewModel.container.viewContext.delete(entity)
        saveData()
    }

    func updateTool(entity: ToolEntity, nametext: String, sntext: String, notestext: String, pickedImage: Data) {
        if !nametext.isEmpty { entity.name = nametext }
        if !sntext.isEmpty { entity.sn = sntext }
        if !notestext.isEmpty { entity.notes = notestext }
        if entity.image != pickedImage { entity.image = pickedImage }
        entity.edit = false
        added2Edit.removeAll()
        print(added2Edit)
        saveData()
    }

    func saveData() {
        do {
            try CoreDataViewModel.container.viewContext.save()
            fetchTools()
        } catch let error {
            print("Error saving. \(error)")
        }
    }

I'm not sure what else to show, but am using this app to learn SwiftUI, so am open to help, critiques, or just down right shame.

Please, help me learn and get better.

Thank you

2      

hi,

since adding and deleting items are working, you must have the basic set-up running correctly.

thus, a quick question: when you toggled the boolean value, did you call saveData()? no changes will be committed to the store and no sync will occur until you do that.

... asking for a friend,

DMG

2      

@delawaremathguy,

great question, and I belive I do.

I guess if would have been smart to include that before, but I was trying not to write a book here.

Here is the relevant code:

func loanTool(loanee: CNContact, dueDate: Date) {
        for entity in added2Cart {
            entity.loanedout = true     // --- here is where I am making the change to the bool
            entity.incart = false           // --- here too
            let dateComponents = Calendar.current.dateComponents([.day, .month, .year], from: dueDate)
            var secondsFromGMT: Int { return TimeZone.current.secondsFromGMT() }
            let mergedComponments = NSDateComponents()
                        mergedComponments.year = dateComponents.year!
                        mergedComponments.month = dateComponents.month!
                        mergedComponments.day = dateComponents.day!
                        mergedComponments.hour = 1
                        mergedComponments.minute = 30
                        mergedComponments.second = 0
            let date = NSCalendar.current.date(from: mergedComponments as DateComponents)
            entity.loandue = date
            let displayedDate = NSCalendar.current.date(from: dateComponents as DateComponents)
            entity.loanduedisplayed = displayedDate
            entity.loanedto = loanee.givenName
            let loaneephonenumbers = loanee.phoneNumbers
            entity.loanedtophone = (loaneephonenumbers.first?.value as? CNPhoneNumber)?.stringValue
            entity.status = "loaned out"
        }
        loanedOut.append(contentsOf: added2Cart)
        added2Cart.removeAll()
        saveData()      // --- save is called here
    }

2      

hi Luke,

sorry, but i don't have an immediate answer here for you, but i would look at a few things.

  • check the console log on device #1 ... are you seeing cloudkit updates flowing from device #1 to the cloud after executing loanTool?
  • check the CloudKit Dashboard to see whether the boolean (and other values) you changed made it properly to the cloud.
  • check the console log on device #2 .... are you seeing the update come down from the cloud, and does it tell you anything (e.g., "no more changes to be made," or even the full cloudkit record that was changed coming in).
  • consider removing the app from both devices (leave the CloudKit container's data as is) and re-installing. that would ensure the tracking histories on the devices are consistent. (perhaps these become inconsistent at some time.)

this is not the most specific information i've given, but perhaps it will still help.

DMG

2      

I have been able to monitor device 1, and do see the cloudkit pushes happening, but they don't appear to be taking in the cloudkit container. when I got there I don't see the bool values updated.

I haven't checked device 2 because I had thought I identified where the problem was, between 1 and cloudkit, but I'm jut kind of lost as to why.

admittedly, I should probably do this all again, and really dig into each log.

also, i tried removing from both for a clean sweep as you suggested, but nothing changed.

I'll go back and look at the logs more closely.

Thanks for you help and advice along the way.

2      

How do you install the apps? Via Xcode or via TestFlight. If you want to sync them on several devices via TestFlight you have to deploy the schema to production. It's also possible you made changes to the data model and forget to deploy the changes to production. Apple Watch doesn't sync without TestFlight at all.

2      

ok, I've found a pretty good work around.

I've made "loan" it's own entity now. and it's updating perfectly...

i do appreciate the advice from everyone however.

to follow up on the last questions:

i had the schema deployed to production. and both devices were getting their image from testflight

working now!

3      

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.