swiftuitableviewcore-dataeditobjectcontext

Editing Core Data List Item Appends Instead of Adding


My To-Do app already had add and delete (from Core Data) functionality. That worked perfect. My problem is that my attempt to add editing is malfunctioning.

Behavior

I coded it so if the user taps a task in the table, it sets that task's cell index path, title, and description to variables and passes them to the modal ViewController for editing. The modal view presents and those passed variables then populate the textfields. The user can then edit the existing content and hit save which runs some save code (I'll share below). The modal view is dismissed, the table data reloaded and the cell appears just where it was before but with updated content. THIS ALL WORKS. The malfunction happens upon a close/full shutdown of the app and reopening it. Suddenly the original task is back BUT a copy with the edits is appended to the bottom of the list.

Question

Why is my code doing this and how can I get the edited title and description to save and load correctly?

Information

My core data file name is: CD_Model My entity name is: TodayTask My attribute names are: 1) "name" 2) "desc"

Code

I included a lot of my code in case the error is somewhere I don't expect. However, I added bold to the headers of the two snippets I think are causing the problems. The error is revealed only upon the viewDidLoad running (first code snippet below). But the error may actually happen in the last snippet, the save function.

Imports & Global variables:

import UIKit
import CoreData

var todayTaskList = [NSManagedObject]()
var passingEdit = false

Declaration and viewDidLoad of main VC, which contains the table:

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
//***** ----- ***** ------ ***** ----- ***** ----- *****
//Initial Setup
//***** ----- ***** ------ ***** ----- ***** ----- *****

@IBOutlet weak var tableView: UITableView!

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.

    //This loads the list from Core Data
    //1
    let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
    let managedContext = appDelegate.managedObjectContext!

    //2
    let fetchRequest = NSFetchRequest(entityName:"TodayTask")

    //3
    var error: NSError?
    let fetchedResults = managedContext.executeFetchRequest(fetchRequest, error: &error) as? [NSManagedObject]

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

    //This provides a variable height for each row
    tableView.rowHeight = UITableViewAutomaticDimension
    tableView.estimatedRowHeight = 80.0
}

Code for creating the table:

//***** ----- ***** ------ ***** ----- ***** ----- *****
//Table View & Cell Setup
//***** ----- ***** ------ ***** ----- ***** ----- *****
@IBOutlet weak var name_Label: UILabel!
@IBOutlet weak var desc_Label: UILabel!

//Tells the table how many rows it should render
//*Looks to the Core Data NSObject to count tasks
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return todayTaskList.count
}

//Creates the individual cells. If the above function returns 3, this runs 3 times
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

    //Setup variables
    let cellIdentifier = "BasicCell"
    let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! CustomTableViewCell
    let task = todayTaskList[indexPath.row]

    //Create table cell with values from Core Data attribute lists
    cell.nameLabel!.text = task.valueForKey("name") as? String
    cell.descLabel!.text = task.valueForKey("desc") as? String

    //Make sure the row heights adjust properly
    tableView.rowHeight = UITableViewAutomaticDimension
    tableView.estimatedRowHeight = 80.0

    return cell
}

Code run when an existing task is tapped:

//***** ----- ***** ------ ***** ----- ***** ----- *****
//Functions
//***** ----- ***** ------ ***** ----- ***** ----- *****

//Action: Edit list item on row tap
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {

    passingEdit = true

    performSegueWithIdentifier("modalToEditor", sender: nil)
}

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    if (segue.identifier == "modalToEditor") && passingEdit == true {

        //Assign selection to a variable 'currentCell'
        let indexPath = tableView.indexPathForSelectedRow();
        let currentCell = tableView.cellForRowAtIndexPath(indexPath!) as! CustomTableViewCell;

        //Set cell text into variables to pass to editor
        var cellNameForEdit = currentCell.nameLabel!.text
        var cellDescForEdit = currentCell.descLabel.text

        //Pass values to EditorView
        var editorVC = segue.destinationViewController as! EditorView;
        editorVC.namePassed = cellNameForEdit
        editorVC.descPassed = cellDescForEdit
        editorVC.indexOfTap = indexPath


    }
}

Declaration of modal Editor VC, variables passed from main VC are set:

class EditorView: UIViewController, UITextFieldDelegate {

//Declare outlets and vars
@IBOutlet var txtTask: UITextField!
@IBOutlet var txtDesc: UITextView!
@IBOutlet weak var addSave: UIButton!
@IBOutlet weak var cancel: UIButton!

var namePassed: String!
var descPassed: String!
var indexOfTap: NSIndexPath!

//Initial Functions
override func viewDidLoad() {
    super.viewDidLoad()

    self.txtTask.becomeFirstResponder()

    if passingEdit == true {
        txtTask.text = namePassed
        txtDesc.text = descPassed
        addSave.setTitle("Save", forState: UIControlState.Normal)
    }
    else {
        addSave.setTitle("Add", forState: UIControlState.Normal)
    }
}

Function run upon hitting the save button:

func modRec(nameValue: String, descValue: String, indexPos: NSIndexPath) {

    //1
    let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
    let managedContext = appDelegate.managedObjectContext!

    //2
    let entity =  NSEntityDescription.entityForName("TodayTask", inManagedObjectContext: managedContext)
    let todayTask = NSManagedObject(entity: entity!, insertIntoManagedObjectContext:managedContext)

    //3
    todayTask.setValue(nameValue, forKey: "name")
    todayTask.setValue(descValue, forKey: "desc")

    //4
    var error: NSError?
    if !managedContext.save(&error) {
        println("Could not save \(error), \(error?.userInfo)")
    }
    //5
    todayTaskList[indexPos.row] = todayTask
    managedContext.save(nil)


}

Solution

  • I have made the following changes in your code. I have added the oldnamevale and olddescvalue as parameter in the function that can be used for predicate. You have to pass these values.

    func modRec(oldnameValue: String, olddescValue: String,newnameValue: String, newdescValue: String, indexPos: NSIndexPath) {
    
        var appDel: AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
        var context: NSManagedObjectContext = appDel.managedObjectContext!
    
        var fetchRequest = NSFetchRequest(entityName: "TodayTask")
    fetchRequest.predicate = NSPredicate(format: "name = %@", oldnameValue)
    
        if let fetchResults = appDel.managedObjectContext!.executeFetchRequest(fetchRequest, error: nil) as? [NSManagedObject] {
          if fetchResults.count != 0{
    
            var managedObject = fetchResults[0]
            managedObject.setValue(newdescValue, forKey: "desc")
            managedObject.setValue(newnameValue, forKey: "name")
    
          context.save(nil)
          }
       }
    
    }
    

    If you want execute query using name and desc then make the following changes in the above code

    let Predicate1 = NSPredicate(format: "name = %@", oldnameValue)
    let Predicate2 = NSPredicate(format: "desc = %@", olddescValue)
    
    var compound = NSCompoundPredicate.andPredicateWithSubpredicates([Predicate1!, Predicate2!])
    fetchRequest.predicate = compound
    

    Hope this might be helpful.