在新的viewcontroller中更改NSManagedObject值

时间:2016-02-18 13:09:21

标签: ios swift core-data nsfetchedresultscontroller nsmanagedobject

我有一个由NSFetchedResultsController管理的UITableView中的客户列表,该类称为CustomersViewController。当我选择一个客户时,会加载一个新的视图控制器CustomerDetailViewController,它会显示它们的详细信息,然后在NSFetchedResultsController管理的另一个UITableView中显示与它们相关的散热器列表。我在表上需要的唯一编辑是删除,这在NSFetchedResultsController管理的两个表中都能正常工作。

我希望能够编辑客户的详细信息,因此我在NavigationBar中有一个编辑按钮,它从CustomerDetailViewController转移到EditCustomerViewController。与之前的segue一样,managedObjectContext和managedObject(选定的客户)成功传递,我可以访问EditCustomerViewController中的所有对象值,我似乎无法编辑它们而不会出现这些错误:

2016-02-18 12:30:08.349 Radiator Calculator[13825:2113477] *** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3512.29.5/UITableView.m:1720
2016-02-18 12:30:08.351 Radiator Calculator[13825:2113477] CoreData: error: Serious application error.  An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:.  Invalid update: invalid number of rows in section 0.  The number of rows contained in an existing section after the update (2) must be equal to the number of rows contained in that section before the update (2), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out). with userInfo (null)

从这个错误中我猜测问题在于NSFetchedResultsController并不喜欢我在EditCustomerViewController中更改两个viewcontrollers之前它实例化的位置。鉴于此视图控制器中没有表,我没有设置它。

有问题的三个视图控制器的代码是:

CustomersViewController的代码:

import UIKit
import CoreData

class CustomersViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, NSFetchedResultsControllerDelegate {

var context: NSManagedObjectContext!

@IBOutlet weak var customerList: UITableView!

var selectedCustomer: NSManagedObject!

// MARK: - viewDidLoad

override func viewDidLoad() {
    super.viewDidLoad()

    print("Customers VC")

    let appDel: AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate

    context = appDel.managedObjectContext

    do {
        try fetchedResultsController.performFetch()
    } catch {
        print("Error occured with FRC")
    }


}

override func viewWillAppear(animated: Bool) {
    //reload todo list data array
    customerList.reloadData()

}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}


//MARK: - Table data functions
func numberOfSectionsInTableView(tableView: UITableView) -> Int {

    if let sections = fetchedResultsController.sections {
        return sections.count
    }

    return 0

}

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

    if let sections = fetchedResultsController.sections {
        let currentSection = sections[section]
        return currentSection.numberOfObjects
    }

    return 0

}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "Customer Cell")

    let customer = fetchedResultsController.objectAtIndexPath(indexPath)

    print(customer)

    cell.textLabel?.text = customer.name

    return cell

}

func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
    if editingStyle == UITableViewCellEditingStyle.Delete {

        let customer = fetchedResultsController.objectAtIndexPath(indexPath) as! NSManagedObject

        context.deleteObject(customer)

        do {
            try context.save()
        } catch let error as NSError {
            print("Error saving context after delete \(error.localizedDescription)")
        }

    }
}

func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    selectedCustomer = fetchedResultsController.objectAtIndexPath(indexPath) as! NSManagedObject

    self.performSegueWithIdentifier("customerDetailSegue", sender: self)

}

// MARK: - Segue
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    print("seg fired")
    if segue.identifier == "addCustomerSegue" {
        if let addCustomerViewController = segue.destinationViewController as? AddCustomerViewController {
            addCustomerViewController.context = context
        }
    }

    if segue.identifier == "customerDetailSegue"{
        if let customerDetailViewController = segue.destinationViewController as? CustomerDetailViewController {
            customerDetailViewController.context = context
            customerDetailViewController.customer = selectedCustomer
        }
    }

}

// set up frc
lazy var fetchedResultsController: NSFetchedResultsController = {

    let customerFetchRequest = NSFetchRequest(entityName: "Customers")
    let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
    customerFetchRequest.sortDescriptors = [sortDescriptor]

    let frc = NSFetchedResultsController(fetchRequest: customerFetchRequest, managedObjectContext: self.context, sectionNameKeyPath: nil, cacheName: nil)

    frc.delegate = self

    return frc
}()


//MARK: NSFetchedResultsControllerDelegate methods
func controllerWillChangeContent(controller: NSFetchedResultsController) {
    self.customerList.beginUpdates()
}

func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
    switch type{
    case NSFetchedResultsChangeType.Insert:
        //note that for insert we insert a row at _newIndexPath_
        if let insertIndexPath = newIndexPath {
            self.customerList.insertRowsAtIndexPaths([insertIndexPath], withRowAnimation: UITableViewRowAnimation.Fade)
        }
    case NSFetchedResultsChangeType.Delete:
        //note that for delete we delete the row at _indexPath_
        if let deleteIndexPath = indexPath {
            self.customerList.deleteRowsAtIndexPaths([deleteIndexPath], withRowAnimation: UITableViewRowAnimation.Fade)
        }
    case NSFetchedResultsChangeType.Update:
        //note that for update we update the row at _indexPath_
        if let updateIndexPath = indexPath {
            let cell = self.customerList.cellForRowAtIndexPath(updateIndexPath)
            let customer = fetchedResultsController.objectAtIndexPath(updateIndexPath)
            cell!.textLabel?.text = customer.name
        }
    case NSFetchedResultsChangeType.Move:
        //note that for Move we delete the row at _indexPath_
        if let deleteIndexPath = indexPath {
            self.customerList.insertRowsAtIndexPaths([deleteIndexPath], withRowAnimation: UITableViewRowAnimation.Fade)
        }
        //note that for move we insert a row at _newIndexPath_
        if let insertIndexPath = newIndexPath {
            self.customerList.insertRowsAtIndexPaths([insertIndexPath], withRowAnimation: UITableViewRowAnimation.Fade)
        }
    }
}

func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
    //note needed as only have one section
}

func controller(controller: NSFetchedResultsController, sectionIndexTitleForSectionName sectionName: String) -> String? {
    return sectionName
}

func controllerDidChangeContent(controller: NSFetchedResultsController) {
    self.customerList.endUpdates()
}



}

和CustomerDetailViewController

import UIKit
import CoreData

class CustomerDetailViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, NSFetchedResultsControllerDelegate {

var context: NSManagedObjectContext!
var customer: NSManagedObject!

@IBOutlet weak var customerName: UILabel!
@IBOutlet weak var street: UILabel!
@IBOutlet weak var town: UILabel!
@IBOutlet weak var postCode: UILabel!
@IBOutlet weak var radiatorList: UITableView!

override func viewDidLoad() {
    super.viewDidLoad()

    radiatorList.allowsSelection = false

    if let name = customer.valueForKey("name") as? String {
        customerName.text = name
    }
    if let addressLine1 = customer.valueForKey("address_line_1") as? String {
        street.text = addressLine1
    }
    if let townName = customer.valueForKey("town") as? String {
        town.text = townName
    }
    if let postcode = customer.valueForKey("postcode") as? String {
        postCode.text = postcode
    }


    // set up FRC
    do {
        try fetchedResultsController.performFetch()
    } catch {
        print("Error occured with FRC")
    }



}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}


// MARK: - Table view data source

func numberOfSectionsInTableView(tableView: UITableView) -> Int {

    if let sections = fetchedResultsController.sections {
        return sections.count
    }

    return 0
}

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

    if let sections = fetchedResultsController.sections {
        let currentSection = sections[section]
        return currentSection.numberOfObjects
    }

    return 0

}


func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("Radiator Cell", forIndexPath: indexPath) as! RadiatorCell

    let radiator = fetchedResultsController.objectAtIndexPath(indexPath) as? NSManagedObject

    if let radiatorName = radiator?.valueForKey("radiatorName") as? String {
        cell.radNameLabel.text = String(radiatorName)
    }

    if let radiatorPowerWatts = radiator?.valueForKey("radiatorPowerWatts") as? Double{
        print(radiatorPowerWatts)
        cell.radPowerWattsLabel.text = "\(ceil(radiatorPowerWatts)) Watts"
        cell.radPowerBtusLabel.text = "\(ceil(radiatorPowerWatts / 0.293)) BTUs"
    }



    return cell
}

/*
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {


}
*/


func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
    if editingStyle == UITableViewCellEditingStyle.Delete {

        let radiator = fetchedResultsController.objectAtIndexPath(indexPath) as! NSManagedObject

        context.deleteObject(radiator)

        do {
            try context.save()
        } catch let error as NSError {
            print("Error saving context after delete \(error.localizedDescription)")
        }

    }
}


// set up frc
lazy var fetchedResultsController: NSFetchedResultsController = {

    let radiatorFetchRequest = NSFetchRequest(entityName: "Radiators")
    let sortDescriptor = NSSortDescriptor(key: "radiatorName", ascending: true)
    radiatorFetchRequest.predicate = NSPredicate(format: "customer = %@", self.customer)

    radiatorFetchRequest.sortDescriptors = [sortDescriptor]

    let frc = NSFetchedResultsController(fetchRequest: radiatorFetchRequest, managedObjectContext: self.context, sectionNameKeyPath: nil, cacheName: nil)

    frc.delegate = self

    return frc
}()


//MARK: NSFetchedResultsControllerDelegate methods
func controllerWillChangeContent(controller: NSFetchedResultsController) {
    self.radiatorList.beginUpdates()
}

func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
    switch type{
    case NSFetchedResultsChangeType.Insert:
        //note that for insert we insert a row at _newIndexPath_
        if let insertIndexPath = newIndexPath {
            self.radiatorList.insertRowsAtIndexPaths([insertIndexPath], withRowAnimation: UITableViewRowAnimation.Fade)
        }
    case NSFetchedResultsChangeType.Delete:
        //note that for delete we delete the row at _indexPath_
        if let deleteIndexPath = indexPath {
            self.radiatorList.deleteRowsAtIndexPaths([deleteIndexPath], withRowAnimation: UITableViewRowAnimation.Fade)
        }
    case NSFetchedResultsChangeType.Update:
        //note that for update we update the row at _indexPath_
        if let updateIndexPath = indexPath {
            let cell = self.radiatorList.cellForRowAtIndexPath(updateIndexPath)
            let radiator = fetchedResultsController.objectAtIndexPath(updateIndexPath)
            cell!.textLabel?.text = radiator.name
        }
    case NSFetchedResultsChangeType.Move:
        //note that for Move we delete the row at _indexPath_
        if let deleteIndexPath = indexPath {
            self.radiatorList.insertRowsAtIndexPaths([deleteIndexPath], withRowAnimation: UITableViewRowAnimation.Fade)
        }
        //note that for move we insert a row at _newIndexPath_
        if let insertIndexPath = newIndexPath {
            self.radiatorList.insertRowsAtIndexPaths([insertIndexPath], withRowAnimation: UITableViewRowAnimation.Fade)
        }
    }
}

func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
    //note needed as only have one section
}

func controller(controller: NSFetchedResultsController, sectionIndexTitleForSectionName sectionName: String) -> String? {
    return sectionName
}

func controllerDidChangeContent(controller: NSFetchedResultsController) {
    self.radiatorList.endUpdates()
}




// MARK: - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    if segue.identifier == "mapSegue"{

        if let mapVC = segue.destinationViewController as? CustomerMapViewController{

            var addressString = String()

            if let addressLine1 = customer.valueForKey("address_line_1") as? String {
                let s = addressLine1
                addressString += "\(s), "
            }
            if let townName = customer.valueForKey("town") as? String {
                let t = townName
                addressString += "\(t), "
            }
            if let postcode = customer.valueForKey("postcode") as? String {
                let p = postcode
                addressString += "\(p)"
            }

            mapVC.address = addressString


        }

    }

    if segue.identifier == "editCustomerSegue" {

        if let editVC = segue.destinationViewController as? EditCustomerViewController {

            editVC.context = context
            editVC.customer = customer

        }

    }

}

}

import UIKit
import CoreData

class EditCustomerViewController: UIViewController {

var context: NSManagedObjectContext!
var customer: NSManagedObject!

override func viewDidLoad() {
    super.viewDidLoad()

    print("Edit customer view controller")

    if let name = customer.valueForKey("name") as? String {
        //this works
        print(name)
    }

    customer.setValue("Hardcoded name change", forKey: "name")





}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}


/*
// MARK: - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    // Get the new view controller using segue.destinationViewController.
    // Pass the selected object to the new view controller.
}
*/

}

应用程序一直运行,直到我最终崩溃时导航回CustomersViewController并且我可以简单地看到列表中的客户名称已更改 - 但是它在CustomerDetailViewController中没有更改。

任何帮助都会很棒,为任何缺乏“swifty-ness”而道歉(我读到这是一件事) - 这是我在swift和iOS中的第一个更大的应用程序,所以我仍然在学习。

0 个答案:

没有答案