How to manage manual ordering of entities in Core Data?

  1. I have a UICollectionView with cells that are loading from CoreData.
  2. User can reorder cells in UICollectionView (it must be reflected in CoreData).
  3. Cells must be loaded in certain order user saved before.


Q: How to save/load order in CoreData safely (I will add iCloud sync later also)?


  • I don't mean overall tips like "add index to entity" or similar. I mean real technique that is 100% stable and ready for synching with iCloud, will not cause any problems and data loss, will not rebuild all data each time etc. Thanks for any help!
  • Maybe Core Data has built-in mechanism to handle that. The only thing I've found is ordered one-to-many relationships, but I completely cannot find any detailed info with examples about how it works and even if that's what I am looking for.

Accepted Reply

hi,


i'm somewhat playing with this right now in a current project, where i have an NSOrderedSet supporting a one-to-many relationship. in my case, it's a high-res image, then one or more thumbnail images for various sizes. so the data for a "Player," say, is defined in the Core Data model, with an relationship "images," a one-to-many with a type "ImageData," where ImageData is really just the data for one image.


Player has this generated variable


var images: NSOrderedSet?


i can add a new Player with this type of code:


let player = Player(context: managedObjectContext)
// fill in some fields
let imageData = ImageData(context: managedObjectContext)
// get some data for the hi-res image
imageData.data = data
player.addToImages(imageData)
// repeat for other images such as thumbnails
let imageData1 = ImageData(context: managedObjectContext)
imageData1.data = data
player.addToImages(imageData1)
// keep on going


i can later retrieve the data using computed properties on Player such as


  var photo: UIImage? {
    get {
      if images!.count == 0 {
        return nil
      }
      let imageData = images![0] as! ImageData // edited after the fact
      return UIImage(data: imageData.data!)
    }
    }


or


  var thumbnail: UIImage? {
    NSLog("Image count is \(images!.count)")
    if images!.count >= 2,
      let imageData = images![1] as? ImageData { // edited after the fact
      return UIImage(data: imageData.data!)
    }
    return nil
  }


this seems to be working for me (although i too struggled a little with this early on in the process).


when you swap two positions in your CollectionView, you'd have to use other accessors to reorder the NSOrderedSet (don't do it by hand, use the accessors); but, full disclosure, i haven't done this since my data has fixed positions in the NSOrderedSet that are set upon creation


hope that helps,

DMG

Replies

hi,


a possible solution is to create a "master" Core Data entity whose only attribute is a one-to-many relationship to the entity that maintains data associated with a cell of the CollectionView.


the ordered array of such an entity would come across in code generation of type NSOrderedSet?. assume, for the moment, that images are loaded to the CollectionView, perhaps via entities named "ImageDataCD", so this "master" Core Data entity would have


var images: NSOrderedSet?


if you set Codegen to Class Definition, you'll see that you then get a whole slew of about 10 accessors to work with the images variable, which include:


    @objc(insertObject:inImagesAtIndex:)
    @NSManaged public func insertIntoImages(_ value: ImageDataCD, at idx: Int)

    @objc(removeObjectFromImagesAtIndex:)
    @NSManaged public func removeFromImages(at idx: Int)

    @objc(replaceImagesAtIndexes:withImages:)
    @NSManaged public func replaceImages(at indexes: NSIndexSet, with values: [ImageDataCD])

    @objc(addImagesObject:)
    @NSManaged public func addToImages(_ value: ImageDataCD)


now you can load up this "master" in the order you want (use addToImages()) when it is created. as the order is changed in the CollectionView, do the appropriate reshuffling (use some combination of removeFromImages(), insertIntoImages(: at), and replaceImages(at: with:) and save the context.


it sounds workable, anyway ... perhaps this is what you were looking for?


hope that helps,

DMG

Yeah, I've tried to find any detailed information about ordered relationships and even created a dummy project trying to understand how it works but no success((( Did not find any working example at all. Maybe you or somebody know how it really works or have seen detailed instruction about how does it work?

Really spent few days trying to figure out how it works... Starting with the question "do I need to override that methods and setup all of them manually?" Because it doesn't work as is(((

hi,


i'm somewhat playing with this right now in a current project, where i have an NSOrderedSet supporting a one-to-many relationship. in my case, it's a high-res image, then one or more thumbnail images for various sizes. so the data for a "Player," say, is defined in the Core Data model, with an relationship "images," a one-to-many with a type "ImageData," where ImageData is really just the data for one image.


Player has this generated variable


var images: NSOrderedSet?


i can add a new Player with this type of code:


let player = Player(context: managedObjectContext)
// fill in some fields
let imageData = ImageData(context: managedObjectContext)
// get some data for the hi-res image
imageData.data = data
player.addToImages(imageData)
// repeat for other images such as thumbnails
let imageData1 = ImageData(context: managedObjectContext)
imageData1.data = data
player.addToImages(imageData1)
// keep on going


i can later retrieve the data using computed properties on Player such as


  var photo: UIImage? {
    get {
      if images!.count == 0 {
        return nil
      }
      let imageData = images![0] as! ImageData // edited after the fact
      return UIImage(data: imageData.data!)
    }
    }


or


  var thumbnail: UIImage? {
    NSLog("Image count is \(images!.count)")
    if images!.count >= 2,
      let imageData = images![1] as? ImageData { // edited after the fact
      return UIImage(data: imageData.data!)
    }
    return nil
  }


this seems to be working for me (although i too struggled a little with this early on in the process).


when you swap two positions in your CollectionView, you'd have to use other accessors to reorder the NSOrderedSet (don't do it by hand, use the accessors); but, full disclosure, i haven't done this since my data has fixed positions in the NSOrderedSet that are set upon creation


hope that helps,

DMG

Thank you DMG! I figured out how it works with your help. It's simple indeed once you get the result, but was no any documentation with examples about that. I've added master and sorting my cells there, works perfect.

Will start with iCloud synch and sharing soon)))

Hey. One year later, I'm curious about what you ended up doing. What I understand from DMG's answer regarding the master object with an NSOrderedSet, it seems to be exactly what iCloud sync WON'T allow, thus requiring some sort of manual ordering, perhaps keeping an index yourself somewhere. This is what I think I have to do now for my project :-/

Yup, it's one year later, and things have changed. i did indeed find out that iCloud does not work with an Ordered relationship.

in the end, there were only two, fixed image data objects i needed: one for a 48 by 48 image, and one for a 144 by 144 image. i just made them attributes of an Entity, rather than separate Entities via a relationship that are indexed 0 ... 1, with a reasonable compression value.

i marked the attributes in Core Data as "Allows External Storage." if i run into problems in the future, i will come back to it (!)

DMG