Editing an item of the main data list is added instead of adding

In My To-Do app already added and removed (from master data). It worked great. My problem is that my attempt to add an edit is not working.

Behavior

I coded it so that if the user deletes the task in the table, it sets the path, title and pointer description to the variables and passes them to the modal ViewController for editing. A modal view is presented and then the passed variables populate the text boxes. The user can then edit the existing content and click "Save", which executes some save code (I'll explain below). The modal view is rejected, the table data is reloaded, and the cell appears only where it was before, but with updated content. IT ALL WORKS. The malfunction occurs when you close / completely disable the application and reopen it. The original task is suddenly reverted, but the modified copy is added 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 main data file name: CD_Model My object name: TodayTask My attribute names: 1) "name" 2) "desc"

code

I've included a lot of my code in case the error is somewhere I don't expect. However, I have added bold to the titles of two snippets that I think are causing problems. The error is only detected when starting viewDidLoad (first code snippet below). But an error can happen in the last snippet, the save function.

Imports and Global Variables:

import UIKit
import CoreData

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

      

Declaration and viewDidLoad of the main VC that 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 to create 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
}

      

Executing code while executing an existing task:

//***** ----- ***** ------ ***** ----- ***** ----- *****
//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


    }
}

      

VC modal editor declaration, 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)
    }
}

      

The function starts when the save button is pressed:

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)


}

      

+3


source to share


4 answers


I made the following changes to your code. I have added oldnamevale and olddescvalue as a parameter in a function that can be used for a predicate. You must 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 to execute the query using name and desc, make the following changes to 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 can be helpful.

+1


source


In your method, modRec

you create a new object TodayTask

:

let todayTask = NSManagedObject(entity: entity!, insertIntoManagedObjectContext:managedContext)

      

and then you replace the object with the appropriate index in the array todayTaskList

:

todayTaskList[indexPos.row] = todayTask

      



But the previous object, whose values โ€‹โ€‹you change, still exists. It is no longer in your array, but it is still in the master data store. When you reload the table view, it uses an array todayTaskList

to populate the rows and you can see what you expect. But when you close and todayTaskList

reopen the application, the array rebuilds itself, fetching from the CoreData store. Since the old TodayTask

and the new exist , they are both added to the array, and hence your table view shows both.

To fix this, I would slightly change the structure of the code:

  • Change view controller EditorView

    : instead of having vars for each of the attributes passed to it, use var for complete NSManagedObject

    . Then you can fill in the text boxes using the attributes of this NSMO. (You will need to change the code prepareForSegue

    accordingly. If you are adding a new one TodayTask

    , then create an NSManagedObject and add it to your array before going to the editor).
  • Then in your method modRec

    you don't need to insert a new object TodayTask

    , you can just set the NSMO var attributes.
  • Since you are modifying an existing NSMO rather than inserting a new one, you do not need to replace the object in the array todayTaskList

    .

Since you no longer need to update the array todayTaskList

from EditorView

, it does not need to be global (which, as a good practice, you should avoid whenever possible). It could just be var in your main controller. (It should also be possible to passingEdit

be global).

+2


source


I think the problem is with the modRec function. You must change the values โ€‹โ€‹of the master data using a predicate. Check out the below example or This link or This tutorial . This might be helpful for you.

func saveLoginData(accessToken: String, userName: String) {
    var appDel: AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
    var context: NSManagedObjectContext = appDel.managedObjectContext!

    var fetchRequest = NSFetchRequest(entityName: "LoginData")
    fetchRequest.predicate = NSPredicate(format: "userName = %@", userName)

    if let fetchResults = appDel.managedObjectContext!.executeFetchRequest(fetchRequest, error: nil) as? [NSManagedObject] {
       if fetchResults.count != 0{

          var managedObject = fetchResults[0]
          managedObject.setValue(accessToken, forKey: "accessToken")

          context.save(nil)
       }
   }
}

      

0


source


1, you should implement methods FetchedResultsControllerDelegate

in your table view and not try to manipulate your own list - this is not the right way to do things and it will cause issues and entries to not display correctly in your UI.

  1. You just need to update the attributes of the original object you got. Your save code creates a new entry.

So, in your editor view, add a property that is set to the original object, not every property.

Now in your edit box, when the user hits save, just update the properties of the original task object and call ManagedObjectContext.Save()

- too easy.

Pass the values ManagedObject

and ManagedObjectContext

into the EditorView

var editorVC = segue.destinationViewController as! EditorView;
editorVC.task = selectedTask
editorVC.moc = managedContext

      

Change view persistence - just update the original task properties (ManagedObject)

       this.task.name = nameField.Text
       this.task.desc = descField.Text

       // That it or commit to disk by calling MOC.Save
       try {
            this.moc.Save()
       }

      

0


source







All Articles