0
votes

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)
    }
}
1
Why are you using key-value coding instead of accessing the properties directly? And for this case the issue is pretty obvious, NSManagedObjectContext doesn't have a property folderPosition, maybe try to set it on your model object instead? ;) This even more emphasise the advantage of accessing properties directly so the compiler directly can tell you if you have an error.Joakim Danielson
Thanks @JoakimDanielson - that makes sense. I basically think I couldn't see the wood for the trees and ended up going on some massively convoluted bug search! ;-)Chris

1 Answers

1
votes

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
}