编辑核心数据列表项追加而不是添加
Editing Core Data List Item Appends Instead of Adding
我的 To-Do 应用程序已经具有添加和删除(从 Core Data)功能。那很完美。我的问题是我尝试添加编辑时出现故障。
行为
我对其进行了编码,因此如果用户点击 table 中的任务,它会将该任务的单元格索引路径、标题和描述设置为变量,并将它们传递给模态 ViewController 进行编辑.模式视图呈现,然后那些传递的变量填充文本字段。然后用户可以编辑现有内容并点击保存,其中 运行 是一些保存代码(我将在下面分享)。模态视图被关闭,table 数据重新加载,单元格出现在它之前的位置但具有更新的内容。这一切都有效。故障发生在应用程序 close/full 关闭并重新打开时。突然,原来的任务又回来了,但是经过编辑的副本被附加到列表的底部。
问题
为什么我的代码会这样?我如何才能正确保存和加载编辑后的标题和描述?
信息
我的核心数据文件名是:CD_Model
我的实体名称是:TodayTask
我的属性名称是:1) "name" 2) "desc"
代码
我包含了很多我的代码,以防错误出现在我意想不到的地方。但是,我在我认为导致问题的两个片段的 headers 中添加了粗体。该错误仅在 viewDidLoad 运行ning 时显示(下面的第一个代码片段)。但是错误可能实际上发生在最后一个片段,即保存函数中。
导入和全局变量:
import UIKit
import CoreData
var todayTaskList = [NSManagedObject]()
var passingEdit = false
mainVC的声明和viewDidLoad,其中包含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
}
创建 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
}
点击现有任务时的代码 运行:
//***** ----- ***** ------ ***** ----- ***** ----- *****
//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,设置从主VC传递的变量:
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)
}
}
函数 运行 点击保存按钮:
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)
}
我认为问题出在 modRec 函数上。您应该使用下面给出的示例 predicate.Check 或 This link or This tutorial 修改核心数据值。可能对你有帮助。
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)
}
}
}
在您的 modRec
方法中,您创建了一个新的 TodayTask
对象:
let todayTask = NSManagedObject(entity: entity!, insertIntoManagedObjectContext:managedContext)
然后替换 todayTaskList
数组中相关索引处的对象:
todayTaskList[indexPos.row] = todayTask
但是您正在修改其值的前一个对象仍然存在。它不再在您的数组中,但它仍在 Core Data 存储中。当您重新加载 table 视图时,它使用 todayTaskList
数组来填充行,您会看到您所期望的。但是当您关闭并重新打开应用程序时,todayTaskList
数组本身会通过从 CoreData 存储中获取来重建。因为旧 todayTask
和新 todayTask
都存在,所以它们都被添加到数组中,因此您的 table 视图显示两者。
为了解决这个问题,我会稍微重组您的代码:
- 修改
EditorView
视图控制器:不是为传递给它的每个属性都使用 var,而是为完整的 NSManagedObject
使用 var。然后,您可以使用该 NSMO 的属性填充文本字段。 (您需要相应地修改 prepareForSegue
代码。如果您要添加新的 TodayTask
然后创建 NSManagedObject,并将其添加到您的数组,然后再转到 EditorView)。
- 然后,在你的
modRec
方法中,你不需要插入一个新的TodayTask
对象,你可以只设置NSMO变量的属性。
- 由于您修改现有的 NSMO,而不是插入一个新的,因此您不需要替换
todayTaskList
数组中的对象。
因为您不再需要从 EditorView
中更新 todayTaskList
数组,所以它不需要是全局的(作为一种好的做法,您应该避免在任何地方做可能的)。它可以只是你主视图控制器中的一个变量。 (也应该可以避免 passingEdit
是全局的)。
1,你应该在你的 tableview 上实现 FetchedResultsControllerDelegate
方法,不要尝试管理你自己的列表 - 这不是正确的做事方式,会导致问题和记录无法正确显示在你的 UI.
- 您只需更新检索到的原始实体的属性。您的保存代码正在创建新记录。
因此在您的编辑器视图中添加一个设置为原始对象的 属性,而不是每个 属性。
现在在您的编辑视图中,当用户点击保存时,只需更新原始任务对象的属性并调用 ManagedObjectContext.Save()
- 太简单了。
将 ManagedObject
和 ManagedObjectContext
值传递给 EditorView
var editorVC = segue.destinationViewController as! EditorView;
editorVC.task = selectedTask
editorVC.moc = managedContext
编辑视图保存 - 只需更新原始任务属性(一个 ManagedObject)
this.task.name = nameField.Text
this.task.desc = descField.Text
// That's it or commit to disk by calling MOC.Save
try {
this.moc.Save()
}
我对您的代码进行了以下更改。
我在可用于谓词的函数中添加了 oldnamevale 和 olddescvalue 作为参数。您必须传递这些值。
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)
}
}
}
如果您想使用 name 和 desc 执行查询,请在上面的代码中进行以下更改
let Predicate1 = NSPredicate(format: "name = %@", oldnameValue)
let Predicate2 = NSPredicate(format: "desc = %@", olddescValue)
var compound = NSCompoundPredicate.andPredicateWithSubpredicates([Predicate1!, Predicate2!])
fetchRequest.predicate = compound
希望这对您有所帮助。
我的 To-Do 应用程序已经具有添加和删除(从 Core Data)功能。那很完美。我的问题是我尝试添加编辑时出现故障。
行为
我对其进行了编码,因此如果用户点击 table 中的任务,它会将该任务的单元格索引路径、标题和描述设置为变量,并将它们传递给模态 ViewController 进行编辑.模式视图呈现,然后那些传递的变量填充文本字段。然后用户可以编辑现有内容并点击保存,其中 运行 是一些保存代码(我将在下面分享)。模态视图被关闭,table 数据重新加载,单元格出现在它之前的位置但具有更新的内容。这一切都有效。故障发生在应用程序 close/full 关闭并重新打开时。突然,原来的任务又回来了,但是经过编辑的副本被附加到列表的底部。
问题
为什么我的代码会这样?我如何才能正确保存和加载编辑后的标题和描述?
信息
我的核心数据文件名是:CD_Model 我的实体名称是:TodayTask 我的属性名称是:1) "name" 2) "desc"
代码
我包含了很多我的代码,以防错误出现在我意想不到的地方。但是,我在我认为导致问题的两个片段的 headers 中添加了粗体。该错误仅在 viewDidLoad 运行ning 时显示(下面的第一个代码片段)。但是错误可能实际上发生在最后一个片段,即保存函数中。
导入和全局变量:
import UIKit
import CoreData
var todayTaskList = [NSManagedObject]()
var passingEdit = false
mainVC的声明和viewDidLoad,其中包含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
}
创建 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
}
点击现有任务时的代码 运行:
//***** ----- ***** ------ ***** ----- ***** ----- *****
//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,设置从主VC传递的变量:
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)
}
}
函数 运行 点击保存按钮:
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)
}
我认为问题出在 modRec 函数上。您应该使用下面给出的示例 predicate.Check 或 This link or This tutorial 修改核心数据值。可能对你有帮助。
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)
}
}
}
在您的 modRec
方法中,您创建了一个新的 TodayTask
对象:
let todayTask = NSManagedObject(entity: entity!, insertIntoManagedObjectContext:managedContext)
然后替换 todayTaskList
数组中相关索引处的对象:
todayTaskList[indexPos.row] = todayTask
但是您正在修改其值的前一个对象仍然存在。它不再在您的数组中,但它仍在 Core Data 存储中。当您重新加载 table 视图时,它使用 todayTaskList
数组来填充行,您会看到您所期望的。但是当您关闭并重新打开应用程序时,todayTaskList
数组本身会通过从 CoreData 存储中获取来重建。因为旧 todayTask
和新 todayTask
都存在,所以它们都被添加到数组中,因此您的 table 视图显示两者。
为了解决这个问题,我会稍微重组您的代码:
- 修改
EditorView
视图控制器:不是为传递给它的每个属性都使用 var,而是为完整的NSManagedObject
使用 var。然后,您可以使用该 NSMO 的属性填充文本字段。 (您需要相应地修改prepareForSegue
代码。如果您要添加新的TodayTask
然后创建 NSManagedObject,并将其添加到您的数组,然后再转到 EditorView)。 - 然后,在你的
modRec
方法中,你不需要插入一个新的TodayTask
对象,你可以只设置NSMO变量的属性。 - 由于您修改现有的 NSMO,而不是插入一个新的,因此您不需要替换
todayTaskList
数组中的对象。
因为您不再需要从 EditorView
中更新 todayTaskList
数组,所以它不需要是全局的(作为一种好的做法,您应该避免在任何地方做可能的)。它可以只是你主视图控制器中的一个变量。 (也应该可以避免 passingEdit
是全局的)。
1,你应该在你的 tableview 上实现 FetchedResultsControllerDelegate
方法,不要尝试管理你自己的列表 - 这不是正确的做事方式,会导致问题和记录无法正确显示在你的 UI.
- 您只需更新检索到的原始实体的属性。您的保存代码正在创建新记录。
因此在您的编辑器视图中添加一个设置为原始对象的 属性,而不是每个 属性。
现在在您的编辑视图中,当用户点击保存时,只需更新原始任务对象的属性并调用 ManagedObjectContext.Save()
- 太简单了。
将 ManagedObject
和 ManagedObjectContext
值传递给 EditorView
var editorVC = segue.destinationViewController as! EditorView;
editorVC.task = selectedTask
editorVC.moc = managedContext
编辑视图保存 - 只需更新原始任务属性(一个 ManagedObject)
this.task.name = nameField.Text
this.task.desc = descField.Text
// That's it or commit to disk by calling MOC.Save
try {
this.moc.Save()
}
我对您的代码进行了以下更改。 我在可用于谓词的函数中添加了 oldnamevale 和 olddescvalue 作为参数。您必须传递这些值。
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)
}
}
}
如果您想使用 name 和 desc 执行查询,请在上面的代码中进行以下更改
let Predicate1 = NSPredicate(format: "name = %@", oldnameValue)
let Predicate2 = NSPredicate(format: "desc = %@", olddescValue)
var compound = NSCompoundPredicate.andPredicateWithSubpredicates([Predicate1!, Predicate2!])
fetchRequest.predicate = compound
希望这对您有所帮助。