Search code examples
swiftcore-datakey-value-coding

CoreData: this class is not key value coding-compliant for the key folderPosition


UPDATED - CODE WITH SOLUTION BELOW

I have used CoreData on a number of different projects without issue but this one has me stumped. The only difference between this and previous projects is I am using CloudKit.

All I am trying to do is set a value for a current record. I have an entity called Folders and in that entity is an attribute named folderPosition which is an Int16.

My code to update the value looks like this:

static func updateFolderPositionsAfterFolderDeletion(managedObjectContext: NSManagedObjectContext = AppDelegate.viewContext) {
    
    let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Folders")
    let sortDescriptor = NSSortDescriptor(key: "folderPosition", ascending: true)
    fetchRequest.sortDescriptors = [sortDescriptor]
    do {
        if let fetchResults = try managedObjectContext.fetch(fetchRequest) as? [Folders] {
            if fetchResults.count != 0 {
                for items in fetchResults {
                   managedObjectContext.setValue(items.folderPosition - 1, forKey: "folderPosition")
                }
            }
        }
        try? managedObjectContext.save()
    }
    catch {
        print("Catch")
    }
}

However I keep getting the following error:

quotelibc++abi.dylib: terminating with uncaught exception of type NSException *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<NSManagedObjectContext 0x600001cb4c30> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key folderPosition.' terminating with uncaught exception of type NSExceptionquote>

The other strange thing is, I have an attribute called folderName (String), if I update with the same code I get no crashes and I can see that it iterates through all the folders available in CoreData and updates the value for folderName but then when I fetch the data back, the folder names have not changed. It's all very odd.

Updated Code Based on Helpful Comments and Answers - Thank You!

static func updateFolderPositionsAfterFolderDeletion(managedObjectContext: NSManagedObjectContext = AppDelegate.viewContext) {
    
    let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Folders")
    let sortDescriptor = NSSortDescriptor(key: "folderPosition", ascending: true)
    fetchRequest.sortDescriptors = [sortDescriptor]
    do {
        if let fetchResults = try managedObjectContext.fetch(fetchRequest) as? [Folders] {
            for (index, folder) in fetchResults.enumerated() {
                folder.folderPosition = (Int16(index))
            }
        }
        try? managedObjectContext.save()
    }
    catch {
        print(error)
    }
}

Solution

  • First of all and unrelated to the issue the logic to reindex the position looks pretty weird. Actually you have to decrement the index only from the item at position deleted position + 1.

    As mentioned in the comments the error is caused by a typo. You mean

    items.setValue(items.folderPosition - 1, forKey: "folderPosition")
    

    instead of

    managedObjectContext.setValue(items.folderPosition - 1, forKey: "folderPosition")
    

    There are many bad practices in the code.

    • The naming is confusing. Name entities and loop elements in singular form: Folder and for item in ....
    • Avoid redundant information. Rename folderPosition to position
    • Prefer dot notation over KVC.
    • Take advantage of the generic fetch request: NSFetchRequest<Folders>.
    • Never check for an empty string or empty array with count == 0. There is isEmpty.
    • The empty check is redundant anyway. The loop is being skipped if the array is empty.
    • Never print meaningless literal strings in catch blocks. Print the error.

    static func updateFolderPositionsAfterFolderDeletion(managedObjectContext: NSManagedObjectContext = AppDelegate.viewContext) {
        
        let fetchRequest = NSFetchRequest<Folders>(entityName: "Folders")
        let sortDescriptor = NSSortDescriptor(key: "folderPosition", ascending: true)
        fetchRequest.sortDescriptors = [sortDescriptor]
        do {
            let folders = try managedObjectContext.fetch(fetchRequest)
            for folder in folders {
               folder.folderPosition = folder.folderPosition - 1
            }
            try managedObjectContext.save()
        }
        catch {
            print(error)
        }
    }
    

    To reindex the entire data set starting at 0 there is a smarter way:

    for (index, folder) in folders.enumerated() {
        folder.folderPosition = index
    }