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)
}
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.