Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Save new order to core data after the using the tableView:moveRowAtIndexPath:toIndexPath: method

I have a tableView in a Swift iOS app that allows the user to re-order the rows. An Edit button is tapped, rows can be re-ordered or deleted, and the Edit button (which now reads Done) is re-tapped to complete the process.

If I simply use the tableView:moveRowAtIndexPath:toIndexPath: method, it works as described above and the table shows the re-ordered rows correctly when Done is tapped. But it doesn't remember the new order when returning to the table, so I added a new "position" variable (Int) in core data and I have made my table sort itself based on that.

My problem is that after a user has moved a row and clicks Done, the table immediately re-orders itself back to how it was before. So it is using the old (original) position variables in core data, before my code can properly capture the new order and re-write it to core data.

Is there a way to make this new order get captured when Done is clicked before the table reloads?

Here is my code that handles the move:

func tableView(tableView: UITableView!, moveRowAtIndexPath sourceIndexPath: NSIndexPath!, toIndexPath destinationIndexPath: NSIndexPath!) {
}

And here is my code that is called when Done is pressed. I go through each row of my table and re-assign the person's position variable in core data based on it's new row:

for currentIteration in 0..<tableView.numberOfRowsInSection(0) {
    var indexPathForCurrentIteration = NSIndexPath(forRow: currentIteration, inSection: 0)
    let personAtCurrentIndexIteration = fetchedResultsController.objectAtIndexPath(indexPathForCurrentIteration) as PersonModel
    personAtCurrentIndexIteration.position = indexPathForCurrentIteration.row
    println("\(personAtCurrentIndexIteration.name) has a position value of \(personAtCurrentIndexIteration.position)")
    (UIApplication.sharedApplication().delegate as AppDelegate).saveContext()
        tableView.reloadData();
}

The above is called when the Done button is pressed, but I also tried calling it within the moveRowAtIndexPath method without success (same result - it reloads the old order of the table when Done is pressed). I have also tried commenting out reloadData without success.

I think my problem might lie in the fact that the above is not capturing the new row order, but instead still gets the old row order. If that is the case, then I don't know the simplest way to make it "wait" to get the new row order.

A longer workaround that I started considering would be to capture the moved row's initial and final row value, and based on that update the remaining rows' values accordingly. Some of that code is below but I stopped since I figured there is likely a smarter way.

if((sourceIndexPath.row > indexPathForCurrentIteration.row) && (destinationIndexPath.row > indexPathForCurrentIteration.row)) {
    //if the moved row was ahead of this row before AND after the move, do nothing (don't change this row's position value)
    //println("\(personAtCurrentIndexIteration.name)'s position was unchnaged")
} else if((sourceIndexPath.row > indexPathForCurrentIteration.row) && (destinationIndexPath.row < indexPathForCurrentIteration.row)) {
    //if the moved row was ahead of this row before BUT behind it after the move, then this row's position value needs to be bumped up one number
    personAtCurrentIndexIteration.position = indexPathForCurrentIteration.row+1
//Etc...

Thanks in advance for any help you can provide.

like image 486
Greg Avatar asked Feb 09 '15 15:02

Greg


2 Answers

  1. Add to CoreData "orderPosition" type Int attribute and add orderPosition to your Model file.

  2. When you add some data to your CoreData, add also orderPosition value

      let appDel: AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
      let contxt: NSManagedObjectContext = appDel.managedObjectContext!
      let en = NSEntityDescription.entityForName("MyCoreData", inManagedObjectContext: contxt)
      let freq = NSFetchRequest(entityName: "MyCoreData")
      var MyData = contxt.executeFetchRequest(freq, error: nil)!
      var newItem = Model(entity: en!, insertIntoManagedObjectContext: contxt)
      var newOrderPosition = MyData.count
    
    
      //here you add some data to your CoreData
      //and also you must add new order position
      newItem.orderPosition = orderPosition
    
  3. In your TableView you must create a function that will sort an array that you will display in your TableView. Something like this:

        func RefreshCoredata()
    {
        let appDel: AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
        let freq = NSFetchRequest(entityName: "MyCoreData")
        let contxt: NSManagedObjectContext = appDel.managedObjectContext!
    
        let sortDescriptor = NSSortDescriptor(key: "orderPosition", ascending: true)
        freq.sortDescriptors = [sortDescriptor]
        MyData = contxt.executeFetchRequest(freq, error: nil)!
    
    } 
    
  4. Remember, you must always sort your array before send him to table!

  5. What you must do when you want to reordering your table rows? :

    override func tableView(tableView: UITableView, canMoveRowAtIndexPath indexPath: NSIndexPath) -> Bool {
    return true
     }
    
    override func tableView(tableView: UITableView, moveRowAtIndexPath fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) {
    
    let appDel: AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
    let contxt: NSManagedObjectContext = appDel.managedObjectContext!
    
    RefreshCoredata()
    
    
    if fromIndexPath.row > toIndexPath.row
    {
        for i in toIndexPath.row..<fromIndexPath.row
    
        {
            MyData[i].setValue(i+1, forKey: "orderPosition")
    
    
        }
    
        MyData[fromIndexPath.row].setValue(toIndexPath.row, forKey: "orderPosition")
    }
    if fromIndexPath.row < toIndexPath.row
    {
    
        for i in fromIndexPath.row + 1...toIndexPath.row
    
        {
            MyData[i].setValue(i-1, forKey: "orderPosition")
    
    
        }
    
        MyData[fromIndexPath.row].setValue(toIndexPath.row, forKey: "orderPosition")
    
    
    
    }
    
    contxt.save(nil)
    
    RefreshCoredata()
    
    }
    

This need to reset ordering position in your CoreData

  1. What you must do when you want to delete some table rows?

    override func tableView(tableView: UITableView, commitEditingStyle editingStyle:
    UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath)
    {
    if editingStyle == .Delete {
    let appDel: AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
    let contxt: NSManagedObjectContext = appDel.managedObjectContext!
    RefreshCoredata()
    contxt.deleteObject(MyData[indexPath.row] as NSManagedObject)
    for i in indexPath.row + 1..<MyData.count
            {
                MyData[i].setValue(i-1, forKey: "orderPosition")
            }
    RefreshCoredata()
    
     tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
     contxt.save(nil)
    
     }
    

That's all!

like image 145
Dmitry Avatar answered Oct 18 '22 01:10

Dmitry


I recently solved the problem in an app that I made that stores a list of Places in Core Data, and these places are transferred to an array Places[] to be shown in the table view.

As mentioned above, you need to create an 'orderID' attribute in your entity.

When you rearrange a row in moveRowAt, make sure you reload the table data.

override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
    let movedObject = places[sourceIndexPath.row]
    places.remove(at: sourceIndexPath.row)
    places.insert(movedObject, at: destinationIndexPath.row)

    tableView.reloadData()
}

This will reload every cell in the table. Therefore, when you are setting the contents of each cell in the cellForRowAt function, then you can set the orderID using the

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "Cell")

    places[indexPath.row].setValue(indexPath.row, forKey: "orderID")
    cell.textLabel?.text = places[indexPath.row].value(forKey: "name") as? String

    return cell

}

Possibly inefficient reloading the data after every move but for small data sets this is fine. Also doesn't work if you need some kind of other ordering as this will just assign each value to the index of the cell where it has been placed currently.

like image 3
Alex Williams Avatar answered Oct 18 '22 02:10

Alex Williams