Search code examples
swiftcore-datansmanagedobjectcontextnssortdescriptor

CoreData Object Inserted at Seemingly Random List Position


I have two tables in separate views, each table's list has a separate entity in CoreData. A user can tap a button within any cell to move it to the other list. This works, items can be moved at a single tap, everything is saved and persists after app shutdown.

Problem

Currently the code is supposed to count the items in the destination list, then append the item being moved to the end of its new list. This doesn't happen. Instead the item is randomly inserted (not at the end or beginning). However, once it is inserted, any further items move will be directly below it. I tried replacing the count function with 1, because I really want the moved item to show at the top anyway, but that doesn't help.

Code

First, here's the viewDidLoad code that runs the load the main list every time the main view appears:

  override func viewDidLoad() {
    super.viewDidLoad()

    currentListEntity = curList

    //Load the list from Core Data
    let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
    let managedContext = appDelegate.managedObjectContext!
    let fetchRequest = NSFetchRequest(entityName:  currentListEntity)
    let sortDescriptor = NSSortDescriptor(key: "displayOrder", ascending: true )
    fetchRequest.sortDescriptors = [ sortDescriptor ]
    var error: NSError?
    let fetchedResults = managedContext.executeFetchRequest(fetchRequest, error: &error) as? [NSManagedObject]


    if let results = fetchedResults {
        taskList_Cntxt = results
    } else {
        println("Could not fetch \(error), \(error!.userInfo)")
    }
 }

Function that I would expect to append the moved object to the end of the new list (which is the entity parameter), but instead inserts it amongst other list items:

func addToList (sender: AnyObject, entity: String) {
    //Setup CoreData context
    let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
    let managedContext = appDelegate.managedObjectContext!

    let entity =  NSEntityDescription.entityForName(entity, inManagedObjectContext: managedContext)
    let task = NSManagedObject(entity: entity!, insertIntoManagedObjectContext:managedContext)

    //Figure out the cell pressed
    let button = sender as! UIButton
    let contentView = button.superview!
    let cell = contentView.superview as! CustomTableViewCell_F2

    //Set cell contents to variables
    var nameValue = cell.nameLabel!.text
    var descValue = cell.descLabel!.text

    //Set content variables to NSObject called "task"
    task.setValue(nameValue, forKey: "name")
    task.setValue(descValue, forKey: "desc")

    //Error check
    var error: NSError?
    if !managedContext.save(&error) {
        println("Could not save \(error), \(error?.userInfo)")
    }
    //Get count, append to end, sort, save
    var insertAt = taskList_Cntxt.count
    addTask(task, displayOrder: insertAt)
    updateDisplayOrder()
    managedContext.save(nil)
}
//Referenced by larger add-task function
func addTask( task: NSManagedObject, displayOrder: Int ) {
    taskList_Cntxt.insert( task, atIndex: displayOrder )
    updateDisplayOrder()
}

//Called to update Core Data after append, delete, and drag/drop
func updateDisplayOrder() {
    for i in 0..<taskList_Cntxt.count {
        let task = taskList_Cntxt[i]
        task.setValue( i, forKey: "displayOrder" )
    }
}

Any ideas on how to fix this? Ideally to append to the top of the list, but at this point appending it to the bottom would be great too.


Solution

  • I did eventually figure this out!

    Before, when I was using the app and looking at the list and tapped 'move to x list' for an item, the item would be added to the destination list somewhere in the middle.

    Now, after fixing it, adding a task from one list to another works great, when I load the destination list the item I sent is always sitting right on top.

    The Solution

    If you look at my code, what I was actually doing was giving the wrong insert address. The error was in two places.

    Error 1

    I was getting a count of the old list and using the count as my insert position:

    //Get count, append to end, sort, save
    var insertAt = taskList_Cntxt.count
    addTask(task, displayOrder: insertAt)
    updateDisplayOrder()
    managedContext.save(nil)
    

    Now I've changed the method to always insert at 1:

    var insertAt = 1
    addTask(task, displayOrder: insertAt)
    update_TargetDisplayOrder()
    managedContext.save(nil)
    

    Error 2

    When I was renumbering the list, I was updating the wrong list:

    func updateDisplayOrder() {
        for i in 0..<taskList_Cntxt.count {
            let task = taskList_Cntxt[i]
            task.setValue( i, forKey: "displayOrder" )
    }
    

    Now I point my add function to an update method that looks at the target list, not the source list:

    func update_TargetDisplayOrder() {
        for i in 0..<targetList_Cntxt.count {
            let task = targetList_Cntxt[i]
            task.setValue( i, forKey: "displayOrder" )
        }
    }
    

    In order to do this, I needed to add a second context variable. Adding targetList_Cntxt allowed me to reorder the new list instead of the old one. After I did that, everything worked.

    Note:

    After the add function runs, the item is deleted from the old list:

    func codeForDelete(sender: AnyObject) {
        //Figure out the cell pressed
        let cell = sender as! CustomTableViewCell
        let indexPath = tableView.indexPathForCell(cell)
    
        //Setup variables for CoreData delete methods
        let appDel: AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
        let context: NSManagedObjectContext = appDel.managedObjectContext!
    
        //Remove the deleted item from the model
        context.deleteObject(taskList_Cntxt[indexPath!.row] as NSManagedObject)
        taskList_Cntxt.removeAtIndex(indexPath!.row)
        context.save(nil)
    
        viewDidLoad()
        println("Delete task function ran")
    }
    

    But as I write this I am realizing I am leaving out a function to update the sort order of the old list once I do the delete. That's something I might need to fix!