【问题标题】:Editing Core Data List Item Appends Instead of Adding编辑核心数据列表项追加而不是添加
【发布时间】:2023-03-28 11:57:01
【问题描述】:

我的待办事项应用程序已经具有添加和删除(来自 Core Data)的功能。这很完美。我的问题是我尝试添加编辑失败了。

行为

我对其进行了编码,因此如果用户点击表格中的任务,它会将任务的单元格索引路径、标题和描述设置为变量,并将它们传递给模态 ViewController 进行编辑。模态视图呈现并且那些传递的变量然后填充文本字段。然后用户可以编辑现有内容并点击运行一些保存代码的保存(我将在下面分享)。模式视图被关闭,表格数据重新加载,单元格出现在它之前的位置,但内容已更新。这一切都有效。故障发生在应用程序关闭/完全关闭并重新打开时。突然,原来的任务又回来了,但是带有编辑的副本被附加到列表的底部。

问题

为什么我的代码会这样做,我怎样才能让编辑的标题和描述正确保存和加载?

信息

我的核心数据文件名是:CD_Model 我的实体名称是:TodayTask 我的属性名称是:1)“name”2)“desc”

代码

我包含了很多我的代码,以防错误出现在我没想到的地方。但是,我在我认为导致问题的两个 sn-ps 的标题中添加了粗体。只有在 viewDidLoad 运行时才会显示错误(下面的第一个代码 sn-p)。但是错误可能实际上发生在最后一个sn-p,即save函数中。

导入和全局变量:

import UIKit
import CoreData

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

主VC的声明和viewDidLoad,包含表:

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


}

【问题讨论】:

    标签: swift uitableview core-data edit objectcontext


    【解决方案1】:

    在您的modRec 方法中,您创建一个新的TodayTask 对象:

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

    然后您替换 todayTaskList 数组中相关索引处的对象:

    todayTaskList[indexPos.row] = todayTask
    

    但您正在修改其值的前一个对象仍然存在。它不再在您的阵列中,但它仍然在核心数据存储中。当您重新加载表视图时,它使用todayTaskList 数组来填充行,您会看到您所期望的。但是当您关闭并重新打开应用程序时,todayTaskList 数组本身会通过从 CoreData 存储中获取来重建。因为旧的todayTask 和新的todayTask 都存在,所以它们都被添加到数组中,因此您的表格视图同时显示两者。

    为了解决这个问题,我会稍微重构你的代码:

    1. 修改EditorView 视图控制器:不要为传递给它的每个属性使用var,而是为完整的NSManagedObject 使用var。然后,您可以使用该 NSMO 的属性填充文本字段。 (您需要相应地修改 prepareForSegue 代码。如果您要添加新的 TodayTask,则创建 NSManagedObject,并将其添加到您的数组中,然后再转到 EditorView)。
    2. 然后,在您的modRec 方法中,您无需插入新的TodayTask 对象,您只需设置NSMO var 的属性即可。
    3. 由于您修改了现有的 NSMO,而不是插入新的,因此您无需替换 todayTaskList 数组中的对象。

    由于您不再需要从 EditorView 中更新 todayTaskList 数组,因此它不需要是全局的(作为一种良好做法,您应该尽可能避免这样做)。它可以只是主视图控制器中的 var。 (也应该可以避免passingEdit 成为全球性的)。

    【讨论】:

    • 我选择了 Karlos 的答案,因为我不知道如何为您的答案编写代码,而他的答案在两分钟内就被剪切/粘贴了。您的回答很好地解释了我的数组如何让我相信我的核心数据存储已更改。没有其他人回答那部分。您还激起了我的好奇心,为什么我应该避免使用全局变量,这是一种不好的做法呢?如果您添加代码以便像我这样的涂料可以实现它,那肯定会让您成为最好的帖子。无论如何,真诚的感谢。
    • @DaveG Globals 违背了面向对象的封装编程原则——对象应该是“黑盒子”,其内部数据和方法对其他对象是隐藏的,除非通过定义良好的接口。在您的情况下,todayTaskList 数组是表视图的数据源,因此应该对其他对象隐藏。因为它是全局的,它可以被所有其他对象访问,所以你的 EditorView 控制器可以在必要时修改它。但是这样做有一个危险:假设您通过从服务器下载它们来添加新的todayTasks....
    • @DaveG .... 您的表格视图控制器可能不知道阵列正在更新,并且表格视图将与您的阵列不同步。或者,如果您作为团队的一员工作,您的一位同事可能会在应用程序的其他地方编写另一个视图控制器,这会与您的数组混淆。这使得调试成为一场噩梦。所以我的建议是尽可能避免使用它们——还有其他可以使用的设计模式(单例、委托等)。但我并不是要说教 - 可能全局变量对你来说是最简单/最好的解决方案,至少目前是这样。
    • pbasdf,虽然听起来很糟糕,但我对此一无所知。遵循最佳实践是我想要养成的习惯,因此我会尽量避免它们。甚至可能会回过头来尝试将这些变量传递给需要它们的 VC。再次感谢
    【解决方案2】:

    我对您的代码进行了以下更改。 我在函数中添加了 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
    

    希望这可能会有所帮助。

    【讨论】:

    • 是的。几乎只是剪切/粘贴。谢谢。
    【解决方案3】:

    我认为问题在于 modRec 功能。您应该使用谓词修改核心数据值。检查下面给出的示例或This linkThis 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)
           }
       }
    }
    

    【讨论】:

    • 我昨晚收到了这段代码,并试图对其进行调整,但未能成功。就我而言,我应该为谓词格式添加什么?也就是说,我应该为“userName = %@”使用什么。提醒一下,我的核心数据Entity name是'TodayTask',属性是'name'和'desc',我设置了var todayTaskList = [NSManagedObject]()
    【解决方案4】:

    1,您应该在您的 tableview 上实现FetchedResultsControllerDelegate 方法,不要尝试管理您自己的列表 - 这不是正确的做事方式,会导致问题和记录无法在您的 UI 中正确显示。

    1. 您只需要更新您检索到的原始实体的属性。您的保存代码正在创建一条新记录。

    因此,在您的编辑器视图中添加一个设置为原始对象的属性,而不是每个属性。

    现在在您的编辑视图中,当用户点击保存时,只需更新原始任务对象的属性并调用 ManagedObjectContext.Save() - 太容易了。

    ManagedObjectManagedObjectContext 值传递给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()
           }
    

    【讨论】:

    • 邓肯,谢谢。我现在正在尝试您的链接和代码。您能否澄清“不要尝试管理自己的列表”的意思?我是 Core Data 的新手,两天前才开始。我在做什么有资格尝试管理我自己的列表?您提供了添加的代码,我认为这不会违反规则 #1,但是我应该删除哪些代码?
    • 好吧,这很好,因为您将避免浪费大量时间。 Core Data 提供了一种在表格视图中显示数据列表的方法,这样添加、删除或编辑任何项目都会自动更新 UI 视图,即使更新是由后台线程完成的。这是一个链接developer.apple.com/library/ios/documentation/CoreData/…
    • 我的示例应用程序有一个这样的例子——只是不是在 Swift 中,而是在 Objective-C 中。但它在结构上非常相似。不过不要费心学习 Obj-C。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-06
    • 1970-01-01
    • 2020-08-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多