Rows in Sections count not updating in TableView - uitableview

I'm having a hard time when I have an item in my UITableView and I update the attribute value that determines the Section.
I have figured out it has something to do with didChangeSection and the option NSFetchedResultsChangeType.Update. But that is as far as I'm able to get.
I'm not sure what code I need to put in there that would update the number of rows in a section.
This is the exact error code I'm getting:
2016-04-17 20:00:37.126 EZ List[13722:1469523] *** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3512.60.7/UITableView.m:1716
2016-04-17 20:00:37.126 EZ List[13722:1469523] 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 (3), plus or minus the number of rows inserted or deleted from that section (0 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)
And this is the code in my view controller:
import UIKit
import CoreData
class ListItemsTableViewController: UITableViewController, NSFetchedResultsControllerDelegate {
let moc = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
var frc: NSFetchedResultsController = NSFetchedResultsController()
var list: Lists?
var catalog: Catalog?
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.reloadData()
self.title = list?.name
frc = getFCR()
frc.delegate = self
do {
try frc.performFetch()
} catch {
print("Failed to perform inital fetch")
}
// Uncomment the following line to preserve selection between presentations
//self.clearsSelectionOnViewWillAppear = true
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem()
}
override func viewWillAppear(animated: Bool) {
let imageView = UIImageView(image: UIImage(named: "TableBackground"))
imageView.contentMode = .ScaleAspectFill
self.tableView.backgroundView = imageView
self.tableView.tableFooterView = UIView(frame: CGRectZero)
}
override func viewDidAppear(animated: Bool) {
frc = getFCR()
frc.delegate = self
do {
try frc.performFetch()
} catch {
fatalError("Failed to perform inital fetch")
}
self.tableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
if let sections = frc.sections {
return sections.count
}
return 0
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let sections = frc.sections {
let currentSection = sections[section]
return currentSection.numberOfObjects
}
return 0
}
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if let sections = frc.sections {
let currentSection = sections[section]
return currentSection.name
}
return nil
}
override func tableView(tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
let header: UITableViewHeaderFooterView = view as! UITableViewHeaderFooterView
header.contentView.backgroundColor = UIColor(red: 84/255, green: 200/255, blue: 214/255, alpha: 0.5)
header.textLabel!.textColor = UIColor.whiteColor()
//header.alpha = 0.5 //make the header transparent
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("listContentCell", forIndexPath: indexPath) as! ListItemsTableViewCell
let item = frc.objectAtIndexPath(indexPath) as! Items
cell.separatorInset = UIEdgeInsets(top: 0, left: 78, bottom: 0, right: 0)
cell.itemName.text = item.name
cell.itemSection.text = item.section
cell.itemQty.text = "Qty: \(item.qty!)"
cell.itemSize.text = item.size
cell.itemPrice.text = floatToCurrency(Float(item.cost!))
cell.itemImage.image = UIImage(data: item.image!)
cell.itemID.text = String(item.id!)
return cell
}
override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
cell.backgroundColor = UIColor.clearColor()
}
override func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? {
let delete = UITableViewRowAction(style: .Destructive, title: "Delete") { (action, indexPath) in
let request = self.fetchRequest()
var fetchResults = [AnyObject]()
do {
fetchResults = try self.moc.executeFetchRequest(request)
} catch {
fatalError("Fetching Data to Delete Failed")
}
self.moc.deleteObject(fetchResults[indexPath.row] as! NSManagedObject)
fetchResults.removeAtIndex(indexPath.row)
do {
try self.moc.save()
} catch {
fatalError("Failed to Save after Delete")
}
}
let edit = UITableViewRowAction(style: .Normal, title: "Edit") { (action, indexPath) in
// Code to come
}
let qty = UITableViewRowAction(style: .Normal, title: "Qty") { (action, indexPath) in
// Code to come
}
edit.backgroundColor = UIColor.init(red: 84/255, green: 200/255, blue: 214/255, alpha: 1)
return [delete, edit, qty]
}
func controllerWillChangeContent(controller: NSFetchedResultsController) {
tableView.beginUpdates()
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
tableView.endUpdates()
}
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type {
case NSFetchedResultsChangeType.Delete:
self.tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Automatic)
case NSFetchedResultsChangeType.Insert:
self.tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: UITableViewRowAnimation.Automatic)
case NSFetchedResultsChangeType.Update:
let cell = self.tableView.cellForRowAtIndexPath(indexPath!) as! ListItemsTableViewCell
let item = self.frc.objectAtIndexPath(indexPath!) as! Items
cell.itemName.text = item.name
cell.itemSection.text = item.section
cell.itemQty.text = "Qty: \(item.qty!)"
cell.itemSize.text = item.size
cell.itemPrice.text = floatToCurrency(Float(item.cost!))
cell.itemImage.image = UIImage(data: item.image!)
cell.itemID.text = String(item.id!)
default:
print("didChangeObject Default was accessed")
break
}
}
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
switch type {
case NSFetchedResultsChangeType.Delete:
self.tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: UITableViewRowAnimation.Automatic)
case NSFetchedResultsChangeType.Insert:
self.tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: UITableViewRowAnimation.Automatic)
case NSFetchedResultsChangeType.Update:
self.tableView.reloadData()
default:
print("didChangeSection Default was accessed")
break
}
}
func fetchRequest() -> NSFetchRequest {
let fetchRequest = NSFetchRequest(entityName: "Items")
let sortDesc1 = NSSortDescriptor(key: "section", ascending: true)
let sortDesc2 = NSSortDescriptor(key: "isChecked", ascending: true)
let sortDesc3 = NSSortDescriptor(key: "name", ascending: true)
fetchRequest.sortDescriptors = [sortDesc1, sortDesc2, sortDesc3]
return fetchRequest
}
func getFCR() -> NSFetchedResultsController {
frc = NSFetchedResultsController(fetchRequest: fetchRequest(), managedObjectContext: moc, sectionNameKeyPath: "section" , cacheName: nil)
return frc
}
func getCatalog(id: NSNumber) -> Catalog {
var cat: Catalog?
let fetchReq = NSFetchRequest(entityName: "Catalog")
let pred = NSPredicate(format: "%K == %#", "id", id)
fetchReq.predicate = pred
do {
let check = try moc.executeFetchRequest(fetchReq)
cat = (check.first as! Catalog)
} catch {
fatalError("Failed fetching Catalog Entry matching Item")
}
return cat!
}
func floatToCurrency(flt: Float) -> String {
let formatter = NSNumberFormatter()
formatter.numberStyle = NSNumberFormatterStyle.CurrencyStyle
return String(formatter.stringFromNumber(flt)!)
}
/*
// Override to support conditional editing of the table view.
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
// Return false if you do not want the specified item to be editable.
return true
}
*/
/*
// Override to support editing the table view.
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
// Delete the row from the data source
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
} else if editingStyle == .Insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
*/
/*
// Override to support rearranging the table view.
override func tableView(tableView: UITableView, moveRowAtIndexPath fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) {
}
*/
/*
// Override to support conditional rearranging of the table view.
override func tableView(tableView: UITableView, canMoveRowAtIndexPath indexPath: NSIndexPath) -> Bool {
// Return false if you do not want the item to be re-orderable.
return true
}
*/
// 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?) {
var id: NSNumber
if (segue.identifier == "listItemView") {
let cell = sender as! UITableViewCell
let indexPath = self.tableView.indexPathForCell(cell)
let itemCont: ViewItemViewController = segue.destinationViewController as! ViewItemViewController
let item: Items = self.frc.objectAtIndexPath(indexPath!) as! Items
itemCont.item = item
id = item.id!
itemCont.catalog = getCatalog(id)
} else if (segue.identifier == "listItemViewEdit") {
let cell = sender as! UITableViewCell
let indexPath = self.tableView.indexPathForCell(cell)
let itemCont: AddItemListViewController = segue.destinationViewController as! AddItemListViewController
let item: Items = self.frc.objectAtIndexPath(indexPath!) as! Items
itemCont.item = item
id = item.id!
itemCont.catalog = getCatalog(id)
itemCont.list = list
}
}
}
I feel like I'm really close to getting it right, but I just need that extra push.

Try reloading the rows in the update you are talking about and check this if it helps
https://forums.developer.apple.com/thread/12184

So it turns out it wasn't a problem with didChangeSection and NSFetchedResultsChangeType.Update. It was a problem with didChangeObject and NSFetchedResultsChangeType.Move.
The following code fixed my issue. Everything else is the same, so I'll just put those two functions.
Added NSFetchedResultsChangeType.Move and inside it I delete the old location and and the new location.
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
switch type {
case NSFetchedResultsChangeType.Delete:
self.tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: UITableViewRowAnimation.Automatic)
case NSFetchedResultsChangeType.Insert:
self.tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: UITableViewRowAnimation.Automatic)
/*case NSFetchedResultsChangeType.Update:
tableView.reloadSections(NSIndexSet(index: sectionIndex), withRowAnimation: UITableViewRowAnimation.Automatic)*/
default:
print("didChangeSection Default was accessed")
break
}
}
Removed NSFetchedResultsChangeType.Update since the move takes care of the Section updating.
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type {
case NSFetchedResultsChangeType.Delete:
self.tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Automatic)
case NSFetchedResultsChangeType.Insert:
self.tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: UITableViewRowAnimation.Automatic)
case NSFetchedResultsChangeType.Update:
let cell = self.tableView.cellForRowAtIndexPath(indexPath!) as! ListItemsTableViewCell
let item = self.frc.objectAtIndexPath(indexPath!) as! Items
cell.itemName.text = item.name
cell.itemSection.text = item.section
cell.itemQty.text = "Qty: \(item.qty!)"
cell.itemSize.text = item.size
cell.itemPrice.text = floatToCurrency(Float(item.cost!))
cell.itemImage.image = UIImage(data: item.image!)
cell.itemID.text = String(item.id!)
case NSFetchedResultsChangeType.Move:
self.tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Automatic)
self.tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: UITableViewRowAnimation.Automatic)
}
}
Hopes this helps someone else!

Related

Swift 3 Sectioned NSFetchResultController

I'm trying to get sections to work in my NSFetchResultController (Using Swift 3). The sections are of a calculated property of a NSManagedObject. The sections are displayed just fine. But every time I try to add a new Object which would generate a new section the app crashes with the following error:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 4. The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (1), 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).'
The FetchResultController part of my TableViewController looks as follows:
lazy var fetchedResultsController: NSFetchedResultsController<Package> = {
let fetchRequest = NSFetchRequest<Package>(entityName:"Package")
let sectionDescriptor = NSSortDescriptor(key: "sectionTitle", ascending: true)
let sortDescriptor = NSSortDescriptor(key: "createdAt", ascending: true)
fetchRequest.sortDescriptors = [sectionDescriptor , sortDescriptor]
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.managedObjectContext, sectionNameKeyPath: "sectionTitle", cacheName: nil)
fetchedResultsController.delegate = self
return fetchedResultsController
}()
override func viewDidLoad() {
super.viewDidLoad()
let appDelegate = UIApplication.shared.delegate as! AppDelegate
managedObjectContext = appDelegate.persistentContainer.viewContext
do {
try self.fetchedResultsController.performFetch()
} catch {
let fetchError = error as NSError
print("\(fetchError), \(fetchError.userInfo)")
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "Package")
do {
try managedObjectContext.fetch(fetchRequest)
} catch let error as NSError {
print("Could not fetch \(error), \(error.userInfo)")
}
}
The TableView Data Source part of my TableViewController looks as follows:
override func numberOfSections(in tableView: UITableView) -> Int {
if let sections = fetchedResultsController.sections {
return sections.count
}
return 0
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let sections = fetchedResultsController.sections {
let sectionInfo = sections[section]
return sectionInfo.numberOfObjects
}
return 1
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if let sections = fetchedResultsController.sections {
let currentSection = sections[section]
return currentSection.name
}
return nil
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier: String = "PackageTableCell"
let cell: PackageTableViewCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) as! PackageTableViewCell
let package = fetchedResultsController.object(at: indexPath)
cell.labelId.text = String(describing: package.packageId)
cell.labelTime.text = DateFormatter.localizedString(from: package.deliveryTime as! Date, dateStyle: DateFormatter.Style.none, timeStyle: DateFormatter.Style.short)
cell.labelWeight.text = String(describing: package.weight)
cell.labelBatch.text = String(describing: package.batch!.batchId)
cell.labelRecepients.text = String(describing: package.recepients)
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRow(at: indexPath as IndexPath, animated: true)
}
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let package: Package = fetchedResultsController.object(at: indexPath)
managedObjectContext.delete(package)
}
}
The FetchResultController Delegate part of my TableViewController looks as follows:
func controllerWillChangeContent() {
tableView.beginUpdates()
}
func controllerDidChangeContent() {
tableView.endUpdates()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
switch type {
case .insert:
tableView.insertSections(NSIndexSet(index: sectionIndex) as IndexSet, with: .fade)
case .delete:
tableView.deleteSections(NSIndexSet(index: sectionIndex) as IndexSet, with: .fade)
case .move:
break
case .update:
break
}
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch (type) {
case .insert:
tableView.insertRows(at: [newIndexPath!], with: .fade)
case .delete:
tableView.deleteRows(at: [indexPath!], with: .fade)
case .update:
let cell = tableView.cellForRow(at: indexPath!) as! PackageTableViewCell
configureCell(cell: cell, atIndexPath: indexPath! as NSIndexPath)
case .move:
tableView.moveRow(at: indexPath!, to: newIndexPath!)
}
}
Then In my AddViewController I save the object as shown below:
#IBAction func savePressed(_ sender: UIBarButtonItem) {
if mode == .add {
let entity = NSEntityDescription.entity(forEntityName: "Package", in: self.managedObjectContext)
package = Package(entity: entity!, insertInto: self.managedObjectContext)
let packageId: Int = UserDefaults.standard.integer(forKey: "packageId")
package?.packageId = Int32(packageId)
UserDefaults.standard.set((packageId +1), forKey: "packageId")
package?.createdAt = NSDate()
package?.location = mapView.userLocation.location! as CLLocation
}
let calendar = NSCalendar.current
let selectedDate = pickerDate.date
let selectedTime = pickerTime.date
var deliveryTimeComponents = DateComponents()
deliveryTimeComponents.year = calendar.component(.year, from: selectedDate)
deliveryTimeComponents.month = calendar.component(.month, from: selectedDate)
deliveryTimeComponents.day = calendar.component(.day, from: selectedDate)
deliveryTimeComponents.hour = calendar.component(.hour, from: selectedTime)
deliveryTimeComponents.minute = calendar.component(.minute, from: selectedTime)
let deliveryTime = calendar.date(from: deliveryTimeComponents)
package?. deliveryTime = deliveryTime as NSDate?
package?.batch = batchPicker?.selectedBatch
package?.weight = Float(inputWeight.text!)!
package?.recepients = Int16(stepperRecepients.value)
package?.notes = textNotes.text!
do {
try package?.managedObjectContext?.save()
if mode == .edit {
packageTransferDelegate?. packageTransfer(transferPackage: package!)
}
dismiss(animated: true, completion: nil)
} catch let error as NSError {
print("Could not save \(error), \(error.userInfo)")
}
}
And finally my Package Class (Package+CoreDataClass)
import Foundation
import CoreData
public class Package: NSManagedObject {
var sectionTitle: String {
return DateFormatter.localizedString(from: self.deliveryTime! as Date, dateStyle: DateFormatter.Style.long, timeStyle: DateFormatter.Style.none)
}
}
Any tips on how I could resolve this problem would be very welcome. Also I'm fairly new to iOS development, so if you see something in my code which you don't like, feel free to write me about it 😉
I found the answer to my own question. I forgot to pass the NSFetchResultContoller into the controllerWillChangeContent and controllerDidChangeContent functions. Which lead to the crash.
I corrected the code as follows:
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates()
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
}
Now everything seems to work just fine.

Cells become blank when scrolling tableview (swift)

I have a strange problem where my cells in the tableview are not consistent. Sometimes they will show as a blank cell and other times they will load with the correct data. See the GIF below.
Notice the blank cell in section 1 changes each time.
I also have this problem when adding new cells to the tableview, but closing and reopening the app always fixes it. It just doesn't load correctly when getting added... but sometimes it does load correctly. See GIF Below.
I've been recommended to use the reloadData(), but that doesn't seem to help anything at all. I'm hoping someone will see this that will know what to do.
See Code Below
Table View Controller: (Swift)
import UIKit
import CoreData
class ListItemsTVC: UITableViewController, NSFetchedResultsControllerDelegate {
// MARK: - Constants and Variables
let moc = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
var frc: NSFetchedResultsController = NSFetchedResultsController()
//var sequeItem: createItem?
// MARK: - App loading Functions
override func viewDidLoad() {
super.viewDidLoad()
frc = getFCR()
frc.delegate = self
do {
try frc.performFetch()
} catch {
print("Failed to perform inital fetch")
}
self.tableView.rowHeight = 62
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.tableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
if let sections = frc.sections {
return sections.count
}
return 0
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let sections = frc.sections {
let currentSection = sections[section]
return currentSection.numberOfObjects
}
return 0
}
override func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 28
}
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if let sections = frc.sections {
let currentSection = sections[section]
return currentSection.name
}
return nil
}
func controllerWillChangeContent(controller: NSFetchedResultsController) {
tableView.beginUpdates()
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
tableView.endUpdates()
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("listContentCell", forIndexPath: indexPath) as! ListItemsTVCell
let item = frc.objectAtIndexPath(indexPath) as! Items
cell.separatorInset = UIEdgeInsets(top: 0, left: 78, bottom: 0, right: 0)
cell.itemName.text = item.name
cell.itemSection.text = item.section
cell.itemQty.text = "Qty: \(item.qty!)"
cell.itemSize.text = item.size
cell.itemPrice.text = floatToCurrency(Float(item.price!))
//cell.itemImage.image = UIImage(data: item.image!)
cell.itemID.text = String(item.id!)
return cell
}
override func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? {
let delete = UITableViewRowAction(style: .Destructive, title: "Delete") { (action, indexPath) in
let item = self.frc.objectAtIndexPath(indexPath) as! Items
let id = item.id!
let request = self.fetchRequest()
let pred = NSPredicate(format: "%K == %#", "id",id)
request.predicate = pred
var fetchResults = [AnyObject]()
do {
fetchResults = try self.moc.executeFetchRequest(request)
} catch {
fatalError("Fetching Data to Delete Failed")
}
self.moc.deleteObject(fetchResults[0] as! NSManagedObject)
fetchResults.removeAtIndex(0)
do {
try self.moc.save()
} catch {
fatalError("Failed to Save after Delete")
}
}
return [delete]
}
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
switch type {
case NSFetchedResultsChangeType.Delete:
self.tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: UITableViewRowAnimation.Automatic)
break
case NSFetchedResultsChangeType.Insert:
self.tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: UITableViewRowAnimation.Automatic)
break
/*case NSFetchedResultsChangeType.Update:
tableView.reloadSections(NSIndexSet(index: sectionIndex), withRowAnimation: UITableViewRowAnimation.Automatic)
break*/
default:
print("Default in didChangeSection was called")
break
}
}
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type {
case NSFetchedResultsChangeType.Delete:
self.tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Automatic)
break
case NSFetchedResultsChangeType.Insert:
self.tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
break
default:
print("Default in didChangeObject was called")
break
}
}
// MARK: - Custom Functions
func fetchRequest() -> NSFetchRequest {
let fetchRequest = NSFetchRequest(entityName: "Items")
let sortDesc1 = NSSortDescriptor(key: "section", ascending: true)
let sortDesc2 = NSSortDescriptor(key: "isChecked", ascending: true)
let sortDesc3 = NSSortDescriptor(key: "name", ascending: true)
fetchRequest.sortDescriptors = [sortDesc1, sortDesc2, sortDesc3]
return fetchRequest
}
func getFCR() -> NSFetchedResultsController {
frc = NSFetchedResultsController(fetchRequest: fetchRequest(), managedObjectContext: moc, sectionNameKeyPath: "section" , cacheName: nil)
return frc
}
func floatToCurrency(flt: Float) -> String {
let formatter = NSNumberFormatter()
formatter.numberStyle = NSNumberFormatterStyle.CurrencyStyle
return String(formatter.stringFromNumber(flt)!)
}
}
Add Button View Controller: (Swift)
import UIKit
import CoreData
class AddItemListVC: UIViewController, NSFetchedResultsControllerDelegate {
// MARK: - Constants and Variables
let moc = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
var sendItem: Items?
// MARK: - App loading Functions
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Outlets and Actions
#IBAction func addItem(sender: AnyObject) {
let entityDesc = NSEntityDescription.entityForName("Items", inManagedObjectContext: moc)
let item = Items(entity: entityDesc!, insertIntoManagedObjectContext: moc)
if (NSUserDefaults.standardUserDefaults().objectForKey("nextItemID") == nil) {
NSUserDefaults.standardUserDefaults().setObject(1, forKey: "nextItemID")
NSUserDefaults.standardUserDefaults().synchronize()
}
let id = NSUserDefaults.standardUserDefaults().integerForKey("nextItemID")
item.id = id
switch id {
case 1..<10:
item.name = "Item ID 00\(id)"
case 10..<100:
item.name = "Item ID 0\(id)"
default:
item.name = "Item ID \(id)"
}
item.brand = "Brand \(id)"
item.qty = 1
item.price = 0
item.size = "Size \(id)"
let sec: Int = Int(arc4random_uniform(UInt32(4 - 1))) + 1
item.section = "Section \(sec)"
item.isChecked = false
do {
try moc.save()
NSUserDefaults.standardUserDefaults().setObject(id + 1, forKey: "nextItemID")
NSUserDefaults.standardUserDefaults().synchronize()
} catch {
fatalError("New item save failed")
}
navigationController!.popViewControllerAnimated(true)
}
}
#Jason Brady, I have just downloaded your code.
There is no problem with you core data, array or table view.
When i run an app in iPhone 5 / iPhone 6 / iPhone 6 Plus with 8.1 it is working fine, none of cell or add button is getting hidden.
But with same devices with 9.2 there is a problem.
Solutions
(1) Custom cell with dequeueReusableCellWithIdentifier
let cell : ListItemsTVCell! = tableView.dequeueReusableCellWithIdentifier("listContentCell", forIndexPath: indexPath) as! ListItemsTVCell
(2) DidselectedRowAtIndex - Here you will get information at cell selection, So data is going perfectly.
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
print("Select")
let myCell = tableView.cellForRowAtIndexPath(indexPath) as! ListItemsTVCell
print(myCell.itemName.text)
}
(3) Problem is with AutoLayout, when i disabled it, all label went to -X positions, when i have aligned it properly using auto resizing, it is now working fine. ( See attached screen shot )
So you need to check with AutoLayout, why it is giving problem in iOS 9 and newer.
Refer link
I hope you can figure out and resolve further.
All the best.
Download
Instead of using
tableView.dequeueReusableCellWithIdentifier("listContentCell", forIndexPath: indexPath)
try using
tableView.dequeueReusableCellWithIdentifier("listContentCell")

New table view cell shows as a blank cell

I've been working on an app, and ran into some problems, so I rebuilt it as simple as possible to find where the error happens at. My code uses CoreData, and my first example has the code for adding new items inside the plus button in the navigation bar. In other words, there is no change of view or segue. This version works as I would expect it to. See the following GIF.
But when I make a new view controller that adds the new item, I end up getting a blank cell instead. I have the exact same code behind the add button that I had in the plus button. And I just use a show segue to get from the tableview to a normal view controller. But if I quit the app and start it back up, it all shows correctly. See below.
If you are curious, I have the section it is adding to being a random number between 1 and 3. My guess is there is something I need to do with that segue but I don't have clue what it is. I'm very new at Swift so I have lots to learn.
Thank you in advance for any help you can give me. I think if I can solve this problem, I can finally move forward with developing my app. Thank you again. Below is my code for the case where it has a segue.
Table View Controller Code (Swift)
import UIKit
import CoreData
class ListItemsTVC: UITableViewController, NSFetchedResultsControllerDelegate {
// MARK: - Constants and Variables
let moc = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
var frc: NSFetchedResultsController = NSFetchedResultsController()
// MARK: - App loading Functions
override func viewDidLoad() {
super.viewDidLoad()
frc = getFCR()
frc.delegate = self
do {
try frc.performFetch()
} catch {
print("Failed to perform inital fetch")
}
self.tableView.rowHeight = 62
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Outlets and Actions
/*#IBAction func addItem(sender: AnyObject) {
let entityDesc = NSEntityDescription.entityForName("Items", inManagedObjectContext: moc)
let item = Items(entity: entityDesc!, insertIntoManagedObjectContext: moc)
if (NSUserDefaults.standardUserDefaults().objectForKey("nextItemID") == nil) {
NSUserDefaults.standardUserDefaults().setObject(1, forKey: "nextItemID")
NSUserDefaults.standardUserDefaults().synchronize()
}
let id = NSUserDefaults.standardUserDefaults().integerForKey("nextItemID")
item.id = id
switch id {
case 1...9:
item.name = "Item ID: 0\(id)"
default:
item.name = "Item ID: \(id)"
}
item.brand = "Brand \(id)"
item.qty = 1
item.price = 0
item.size = "Size \(id)"
let sec: Int = Int(arc4random_uniform(UInt32(4 - 1))) + 1
item.section = "Section \(sec)"
item.isChecked = false
do {
try moc.save()
NSUserDefaults.standardUserDefaults().setObject(id + 1, forKey: "nextItemID")
NSUserDefaults.standardUserDefaults().synchronize()
} catch {
fatalError("New item save failed")
}
}*/
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
if let sections = frc.sections {
return sections.count
}
return 0
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let sections = frc.sections {
let currentSection = sections[section]
return currentSection.numberOfObjects
}
return 0
}
override func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 28
}
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if let sections = frc.sections {
let currentSection = sections[section]
return currentSection.name
}
return nil
}
func controllerWillChangeContent(controller: NSFetchedResultsController) {
tableView.beginUpdates()
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
tableView.endUpdates()
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("listContentCell", forIndexPath: indexPath) as! ListItemsTVCell
let item = frc.objectAtIndexPath(indexPath) as! Items
cell.separatorInset = UIEdgeInsets(top: 0, left: 78, bottom: 0, right: 0)
cell.itemName.text = item.name
cell.itemSection.text = item.section
cell.itemQty.text = "Qty: \(item.qty!)"
cell.itemSize.text = item.size
cell.itemPrice.text = floatToCurrency(Float(item.price!))
//cell.itemImage.image = UIImage(data: item.image!)
cell.itemID.text = String(item.id!)
return cell
}
override func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? {
let delete = UITableViewRowAction(style: .Destructive, title: "Delete") { (action, indexPath) in
let item = self.frc.objectAtIndexPath(indexPath) as! Items
let id = item.id!
let request = self.fetchRequest()
let pred = NSPredicate(format: "%K == %#", "id",id)
request.predicate = pred
var fetchResults = [AnyObject]()
do {
fetchResults = try self.moc.executeFetchRequest(request)
} catch {
fatalError("Fetching Data to Delete Failed")
}
self.moc.deleteObject(fetchResults[0] as! NSManagedObject)
fetchResults.removeAtIndex(0)
do {
try self.moc.save()
} catch {
fatalError("Failed to Save after Delete")
}
}
return [delete]
}
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
switch type {
case NSFetchedResultsChangeType.Delete:
self.tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: UITableViewRowAnimation.Automatic)
case NSFetchedResultsChangeType.Insert:
self.tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: UITableViewRowAnimation.Automatic)
/*case NSFetchedResultsChangeType.Update:
tableView.reloadSections(NSIndexSet(index: sectionIndex), withRowAnimation: UITableViewRowAnimation.Automatic)*/
default:
print("Default in didChangeSection was called")
break
}
}
internal func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type {
case NSFetchedResultsChangeType.Delete:
self.tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Automatic)
case NSFetchedResultsChangeType.Insert:
self.tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
default:
print("Default in didChangeObject was called")
break
}
}
/*
// Override to support conditional editing of the table view.
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
// Return false if you do not want the specified item to be editable.
return true
}
*/
/*
// Override to support editing the table view.
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
// Delete the row from the data source
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
} else if editingStyle == .Insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
*/
/*
// Override to support rearranging the table view.
override func tableView(tableView: UITableView, moveRowAtIndexPath fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) {
}
*/
/*
// Override to support conditional rearranging of the table view.
override func tableView(tableView: UITableView, canMoveRowAtIndexPath indexPath: NSIndexPath) -> Bool {
// Return false if you do not want the item to be re-orderable.
return true
}
*/
/*
// 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.
}
*/
// MARK: - Custom Functions
func fetchRequest() -> NSFetchRequest {
let fetchRequest = NSFetchRequest(entityName: "Items")
let sortDesc1 = NSSortDescriptor(key: "section", ascending: true)
let sortDesc2 = NSSortDescriptor(key: "isChecked", ascending: true)
let sortDesc3 = NSSortDescriptor(key: "name", ascending: true)
fetchRequest.sortDescriptors = [sortDesc1, sortDesc2, sortDesc3]
return fetchRequest
}
func getFCR() -> NSFetchedResultsController {
frc = NSFetchedResultsController(fetchRequest: fetchRequest(), managedObjectContext: moc, sectionNameKeyPath: "section" , cacheName: nil)
return frc
}
func floatToCurrency(flt: Float) -> String {
let formatter = NSNumberFormatter()
formatter.numberStyle = NSNumberFormatterStyle.CurrencyStyle
return String(formatter.stringFromNumber(flt)!)
}
}
View Controller (Swift) [The segue view]
import UIKit
import CoreData
class AddItemListVC: UIViewController, NSFetchedResultsControllerDelegate {
// MARK: - Constants and Variables
let moc = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
//var frc: NSFetchedResultsController = NSFetchedResultsController()
// MARK: - App loading Functions
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Outlets and Actions
#IBAction func addItem(sender: AnyObject) {
let entityDesc = NSEntityDescription.entityForName("Items", inManagedObjectContext: moc)
let item = Items(entity: entityDesc!, insertIntoManagedObjectContext: moc)
if (NSUserDefaults.standardUserDefaults().objectForKey("nextItemID") == nil) {
NSUserDefaults.standardUserDefaults().setObject(1, forKey: "nextItemID")
NSUserDefaults.standardUserDefaults().synchronize()
}
let id = NSUserDefaults.standardUserDefaults().integerForKey("nextItemID")
item.id = id
switch id {
case 1...9:
item.name = "Item ID: 0\(id)"
default:
item.name = "Item ID: \(id)"
}
item.brand = "Brand \(id)"
item.qty = 1
item.price = 0
item.size = "Size \(id)"
let sec: Int = Int(arc4random_uniform(UInt32(4 - 1))) + 1
item.section = "Section \(sec)"
item.isChecked = false
do {
try moc.save()
NSUserDefaults.standardUserDefaults().setObject(id + 1, forKey: "nextItemID")
NSUserDefaults.standardUserDefaults().synchronize()
} catch {
fatalError("New item save failed")
}
}
/*
// 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.
}
*/
}
Reload TableView Data is viewWillAppear method as :
override func viewWillAppear() {
super.viewWillAppear()
do {
try frc.performFetch()
} catch {
print("Failed to perform inital fetch")
}
}
As when you are navigating back from viewController you are not telling the TableViewController to update tableView so do it in viewWillAppear as it will fire when you are popping navController to come back.
Here is the code that fixed the problem for me. I only changed the Table View Controller Code:
import UIKit
import CoreData
class ListItemsTVC: UITableViewController, NSFetchedResultsControllerDelegate {
// MARK: - Constants and Variables
let moc = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
var frc: NSFetchedResultsController = NSFetchedResultsController()
// MARK: - App loading Functions
override func viewDidLoad() {
super.viewDidLoad()
frc = getFCR()
frc.delegate = self
do {
try frc.performFetch()
} catch {
print("Failed to perform inital fetch")
}
self.tableView.rowHeight = 62
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.tableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Outlets and Actions
/*#IBAction func addItem(sender: AnyObject) {
let entityDesc = NSEntityDescription.entityForName("Items", inManagedObjectContext: moc)
let item = Items(entity: entityDesc!, insertIntoManagedObjectContext: moc)
if (NSUserDefaults.standardUserDefaults().objectForKey("nextItemID") == nil) {
NSUserDefaults.standardUserDefaults().setObject(1, forKey: "nextItemID")
NSUserDefaults.standardUserDefaults().synchronize()
}
let id = NSUserDefaults.standardUserDefaults().integerForKey("nextItemID")
item.id = id
switch id {
case 1...9:
item.name = "Item ID: 0\(id)"
default:
item.name = "Item ID: \(id)"
}
item.brand = "Brand \(id)"
item.qty = 1
item.price = 0
item.size = "Size \(id)"
let sec: Int = Int(arc4random_uniform(UInt32(4 - 1))) + 1
item.section = "Section \(sec)"
item.isChecked = false
do {
try moc.save()
NSUserDefaults.standardUserDefaults().setObject(id + 1, forKey: "nextItemID")
NSUserDefaults.standardUserDefaults().synchronize()
} catch {
fatalError("New item save failed")
}
}*/
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
if let sections = frc.sections {
return sections.count
}
return 0
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let sections = frc.sections {
let currentSection = sections[section]
return currentSection.numberOfObjects
}
return 0
}
override func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 28
}
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if let sections = frc.sections {
let currentSection = sections[section]
return currentSection.name
}
return nil
}
func controllerWillChangeContent(controller: NSFetchedResultsController) {
tableView.beginUpdates()
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
tableView.endUpdates()
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("listContentCell", forIndexPath: indexPath) as! ListItemsTVCell
let item = frc.objectAtIndexPath(indexPath) as! Items
cell.separatorInset = UIEdgeInsets(top: 0, left: 78, bottom: 0, right: 0)
cell.itemName.text = item.name
cell.itemSection.text = item.section
cell.itemQty.text = "Qty: \(item.qty!)"
cell.itemSize.text = item.size
cell.itemPrice.text = floatToCurrency(Float(item.price!))
//cell.itemImage.image = UIImage(data: item.image!)
cell.itemID.text = String(item.id!)
return cell
}
override func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? {
let delete = UITableViewRowAction(style: .Destructive, title: "Delete") { (action, indexPath) in
let item = self.frc.objectAtIndexPath(indexPath) as! Items
let id = item.id!
let request = self.fetchRequest()
let pred = NSPredicate(format: "%K == %#", "id",id)
request.predicate = pred
var fetchResults = [AnyObject]()
do {
fetchResults = try self.moc.executeFetchRequest(request)
} catch {
fatalError("Fetching Data to Delete Failed")
}
self.moc.deleteObject(fetchResults[0] as! NSManagedObject)
fetchResults.removeAtIndex(0)
do {
try self.moc.save()
} catch {
fatalError("Failed to Save after Delete")
}
}
return [delete]
}
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
switch type {
case NSFetchedResultsChangeType.Delete:
self.tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: UITableViewRowAnimation.Automatic)
case NSFetchedResultsChangeType.Insert:
self.tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: UITableViewRowAnimation.Automatic)
/*case NSFetchedResultsChangeType.Update:
tableView.reloadSections(NSIndexSet(index: sectionIndex), withRowAnimation: UITableViewRowAnimation.Automatic)*/
default:
print("Default in didChangeSection was called")
break
}
}
internal func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type {
case NSFetchedResultsChangeType.Delete:
self.tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Automatic)
case NSFetchedResultsChangeType.Insert:
self.tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
default:
print("Default in didChangeObject was called")
break
}
}
/*
// Override to support conditional editing of the table view.
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
// Return false if you do not want the specified item to be editable.
return true
}
*/
/*
// Override to support editing the table view.
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
// Delete the row from the data source
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
} else if editingStyle == .Insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
*/
/*
// Override to support rearranging the table view.
override func tableView(tableView: UITableView, moveRowAtIndexPath fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) {
}
*/
/*
// Override to support conditional rearranging of the table view.
override func tableView(tableView: UITableView, canMoveRowAtIndexPath indexPath: NSIndexPath) -> Bool {
// Return false if you do not want the item to be re-orderable.
return true
}
*/
/*
// 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.
}
*/
// MARK: - Custom Functions
func fetchRequest() -> NSFetchRequest {
let fetchRequest = NSFetchRequest(entityName: "Items")
let sortDesc1 = NSSortDescriptor(key: "section", ascending: true)
let sortDesc2 = NSSortDescriptor(key: "isChecked", ascending: true)
let sortDesc3 = NSSortDescriptor(key: "name", ascending: true)
fetchRequest.sortDescriptors = [sortDesc1, sortDesc2, sortDesc3]
return fetchRequest
}
func getFCR() -> NSFetchedResultsController {
frc = NSFetchedResultsController(fetchRequest: fetchRequest(), managedObjectContext: moc, sectionNameKeyPath: "section" , cacheName: nil)
return frc
}
func floatToCurrency(flt: Float) -> String {
let formatter = NSNumberFormatter()
formatter.numberStyle = NSNumberFormatterStyle.CurrencyStyle
return String(formatter.stringFromNumber(flt)!)
}
}
Muhammad's answer will surely solve the problem as it will trigger the reload of the table view every time viewWillAppear will call. But the problem is that is not a very good way of doing this.
You see, viewWillAppear gets called a lot of times. It is a UIViewController life cycle method. If you are going to the second view controller and coming back without pressing the add button, it will still reload the UITableView. Now, the point is not whether it is an expensive operation or not, the point is that viewWillAppear is not the place to do that kind of a stuff. What if you had to do something else before reloading the cell, you may not want to put it all there and risk creating bugs.
A good way of achieving this is through delegates (call backs). Read up more on this and you will see that it gives you a much needed flexibility.

Cell formats incorrectly when inserting row

I'm having a very interesting event happening when I load a new item into Core data / UITableView.
One thing to note is that before I didn't have Sections implemented and I never had anything like this happening. But after I added in sections, it randomly started.
It only happens right when I add in (insert) a new entry into Core data / UITableView. If I go back to the previous View Controller and then go back to the table, it will automatically fix it's self.
Here is a picture of what it is currently doing when I add a new row.
As you can see on the left side, a few of the labels, Image, and Button, are scattered, and on the right side things are all where they belong.
Everything has been fully constrained and works on all the various phone sizes. But like I said, it all started happening after I added in the section feature.
I'm assuming that only the code for the TableViewController is needed, so please see below.
import UIKit
import CoreData
class ListItemsTableViewController: UITableViewController, NSFetchedResultsControllerDelegate {
let moc = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
var frc: NSFetchedResultsController = NSFetchedResultsController()
var list: Lists?
var catalog: Catalog?
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.reloadData()
self.title = list?.name
frc = getFCR()
frc.delegate = self
do {
try frc.performFetch()
} catch {
print("Failed to perform inital fetch")
}
// Uncomment the following line to preserve selection between presentations
//self.clearsSelectionOnViewWillAppear = true
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem()
}
override func viewWillAppear(animated: Bool) {
let imageView = UIImageView(image: UIImage(named: "TableBackground"))
imageView.contentMode = .ScaleAspectFill
self.tableView.backgroundView = imageView
self.tableView.tableFooterView = UIView(frame: CGRectZero)
self.tableView.reloadData()
}
override func viewDidAppear(animated: Bool) {
frc = getFCR()
frc.delegate = self
do {
try frc.performFetch()
} catch {
fatalError("Failed to perform inital fetch")
}
self.tableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
if let sections = frc.sections {
return sections.count
}
return 0
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let sections = frc.sections {
let currentSection = sections[section]
return currentSection.numberOfObjects
}
return 0
}
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if let sections = frc.sections {
let currentSection = sections[section]
return currentSection.name
}
return nil
}
override func tableView(tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
let header: UITableViewHeaderFooterView = view as! UITableViewHeaderFooterView
header.contentView.backgroundColor = UIColor(red: 84/255, green: 200/255, blue: 214/255, alpha: 0.5)
header.textLabel!.textColor = UIColor.whiteColor()
//header.alpha = 0.5 //make the header transparent
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("listContentCell", forIndexPath: indexPath) as! ListItemsTableViewCell
let item = frc.objectAtIndexPath(indexPath) as! Items
cell.separatorInset = UIEdgeInsets(top: 0, left: 78, bottom: 0, right: 0)
cell.itemName.text = item.name
cell.itemSection.text = item.section
cell.itemQty.text = "Qty: \(item.qty!)"
cell.itemSize.text = item.size
cell.itemPrice.text = floatToCurrency(Float(item.cost!))
cell.itemImage.image = UIImage(data: item.image!)
cell.itemID.text = String(item.id!)
return cell
}
override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
cell.backgroundColor = UIColor.clearColor()
}
override func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? {
let delete = UITableViewRowAction(style: .Destructive, title: "Delete") { (action, indexPath) in
let request = self.fetchRequest()
var fetchResults = [AnyObject]()
do {
fetchResults = try self.moc.executeFetchRequest(request)
} catch {
fatalError("Fetching Data to Delete Failed")
}
self.moc.deleteObject(fetchResults[indexPath.row] as! NSManagedObject)
fetchResults.removeAtIndex(indexPath.row)
do {
try self.moc.save()
} catch {
fatalError("Failed to Save after Delete")
}
}
let edit = UITableViewRowAction(style: .Normal, title: "Edit") { (action, indexPath) in
// Code to come
}
let qty = UITableViewRowAction(style: .Normal, title: "Qty") { (action, indexPath) in
// Code to come
}
edit.backgroundColor = UIColor.init(red: 84/255, green: 200/255, blue: 214/255, alpha: 1)
return [delete, edit, qty]
}
func controllerWillChangeContent(controller: NSFetchedResultsController) {
tableView.beginUpdates()
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
tableView.endUpdates()
}
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
switch type {
case NSFetchedResultsChangeType.Delete:
self.tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: UITableViewRowAnimation.Automatic)
case NSFetchedResultsChangeType.Insert:
self.tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: UITableViewRowAnimation.Automatic)
/*case NSFetchedResultsChangeType.Update:
tableView.reloadSections(NSIndexSet(index: sectionIndex), withRowAnimation: UITableViewRowAnimation.Automatic)*/
default:
print("didChangeSection Default was accessed")
break
}
}
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type {
case NSFetchedResultsChangeType.Delete:
self.tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Automatic)
case NSFetchedResultsChangeType.Insert:
self.tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: UITableViewRowAnimation.Automatic)
self.tableView.reloadData()
case NSFetchedResultsChangeType.Update:
let cell = self.tableView.cellForRowAtIndexPath(indexPath!) as! ListItemsTableViewCell
let item = self.frc.objectAtIndexPath(indexPath!) as! Items
cell.itemName.text = item.name
cell.itemSection.text = item.section
cell.itemQty.text = "Qty: \(item.qty!)"
cell.itemSize.text = item.size
cell.itemPrice.text = floatToCurrency(Float(item.cost!))
cell.itemImage.image = UIImage(data: item.image!)
cell.itemID.text = String(item.id!)
case NSFetchedResultsChangeType.Move:
self.tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Automatic)
self.tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: UITableViewRowAnimation.Automatic)
}
}
func fetchRequest() -> NSFetchRequest {
let fetchRequest = NSFetchRequest(entityName: "Items")
let sortDesc1 = NSSortDescriptor(key: "section", ascending: true)
let sortDesc2 = NSSortDescriptor(key: "isChecked", ascending: true)
let sortDesc3 = NSSortDescriptor(key: "name", ascending: true)
fetchRequest.sortDescriptors = [sortDesc1, sortDesc2, sortDesc3]
return fetchRequest
}
func getFCR() -> NSFetchedResultsController {
frc = NSFetchedResultsController(fetchRequest: fetchRequest(), managedObjectContext: moc, sectionNameKeyPath: "section" , cacheName: nil)
return frc
}
func getCatalog(id: NSNumber) -> Catalog {
var cat: Catalog?
let fetchReq = NSFetchRequest(entityName: "Catalog")
let pred = NSPredicate(format: "%K == %#", "id", id)
fetchReq.predicate = pred
do {
let check = try moc.executeFetchRequest(fetchReq)
cat = (check.first as! Catalog)
} catch {
fatalError("Failed fetching Catalog Entry matching Item")
}
return cat!
}
func floatToCurrency(flt: Float) -> String {
let formatter = NSNumberFormatter()
formatter.numberStyle = NSNumberFormatterStyle.CurrencyStyle
return String(formatter.stringFromNumber(flt)!)
}
// 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?) {
var id: NSNumber
if (segue.identifier == "listItemView") {
let cell = sender as! UITableViewCell
let indexPath = self.tableView.indexPathForCell(cell)
let itemCont: ViewItemViewController = segue.destinationViewController as! ViewItemViewController
let item: Items = self.frc.objectAtIndexPath(indexPath!) as! Items
itemCont.item = item
id = item.id!
itemCont.catalog = getCatalog(id)
} else if (segue.identifier == "listItemViewEdit") {
let cell = sender as! UITableViewCell
let indexPath = self.tableView.indexPathForCell(cell)
let itemCont: AddItemListViewController = segue.destinationViewController as! AddItemListViewController
let item: Items = self.frc.objectAtIndexPath(indexPath!) as! Items
itemCont.item = item
id = item.id!
itemCont.catalog = getCatalog(id)
itemCont.list = list
}
}
}
Like I said previously, this wasn't a problem till I added in Sections. But when I click the the back button (< Lists) and then go back to this view, it will automatically correct itself to what is shown on the right.
Also, it doesn't do it every time. Sometimes it'll just do it the first time I add an item when entering the TableView. And it also doesn't seem to do it to items that get added outside the view. i.e. rows that exist well below the viewable area of the screen.
Editing and deleting doesn't seem to cause this problem either, just adding new items.
If I were to guess, somehow the UIImage is getting a small or zero size and it's breaking everything else. But the Size Label and Price Label ($0.00) are directly linked the Qty Label which never seems to move.
So it seems like if the UIImage were messing things up, it would also mess up the Qty Label too.
It's not an app breaker, but it looks really crapy and dumb that it has to be reloaded by exiting the ViewController then going back in. (also, I've tried putting self.tableView.reloaddata() in multiple different places but none seem to fix the problem.)
EDIT:
Removed all constraints and remade them again. It only made things worse, see image below.
EDIT 2:
Added constraints image
EDIT 3:
I'm guessing at this point, the post is dead and I'll have to go elsewhere to find answers to what is going on, but I was able to narrow it down to when the error specifically comes into play. When looking at one of my older backups of my app, where I was not having this issue, I slowly edited it till I started getting the problem. And my error comes in from the didChangeObject function. Before, I had code like this
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
if (type == .delete) {
self.tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Automatic)
}
}
And my cell would format correctly, but as soon as I updated it to be this
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type {
case NSFetchedResultsChangeType.Delete:
self.tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Automatic)
case NSFetchedResultsChangeType.Insert:
self.tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: UITableViewRowAnimation.Automatic)
case NSFetchedResultsChangeType.Update:
let cell = self.tableView.cellForRowAtIndexPath(indexPath!) as! ListItemsTableViewCell
let item = self.frc.objectAtIndexPath(indexPath!) as! Items
cell.itemName.text = item.name
cell.itemSection.text = item.section
cell.itemQty.text = "Qty: \(item.qty!)"
cell.itemSize.text = item.size
cell.itemPrice.text = floatToCurrency(Float(item.cost!))
cell.itemImage.image = UIImage(data: item.image!)
cell.itemID.text = String(item.id!)
case NSFetchedResultsChangeType.Move:
self.tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Automatic)
self.tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: UITableViewRowAnimation.Automatic)
}
}
My cell would no longer format correctly. After further testing, I found that the move type also created the same formatting error, so my problem is specifically caused by self.tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: UITableViewRowAnimation.Automatic). The main reason I switched from what I had to to the longer statement is when I added in sections, I would get an error saying that the sections before and after didn't match (specifically when a entry was modified to where it would switch to a new section), and from everything I can find online the longer way is the correct way to do it... but for some reason it causes formatting errors on my application. Could be my code (very likely since I'm new at all of this) or could be an bug with Xcode.
I don't plan to give up on this, but I can only bang my head against the wall for so long before I start to loose my mind, so I'll probably take a break for a while.
I'd definitely appreciate any suggestions if you have any though. Hope this post somehow hopes someone else.
What i can guess is that there might be some problem with your viewDidLoad() and viewWillAppear() functions , you can learn about the sequence they get executed like viewDidLoad() is called only one time when view appears for first time and than the work is upto viewWillAppear() , i guess that's where you have image code ,that corrects the things once you come to this view second times . And i have little questions like you are using storyboard for size ,price and quantity labels and your image view right or not ?
And try to use some breakpoints for knowing the difference that how your code gets correct view second point . Like you can put breakpoints in important functions like viewDidload(), viewDidAppear() and viewWillAppear.
The constraints in this image work for me .
Here is the output.
and using print(cell.itemImage.frame.size.width) in cellForRowAtIndexPath gave me the width of 61 and i got the same height 61 as well .
You can show me the snapshot of constraints like i have shown and i can tell you what's wrong .
In the end, it ended up being that I needed to turn off Size Classes. I don't fully understand why this was a problem, but it has fixed everything.

Changing NSManagedObject values in new viewcontroller

I have a list of customers in a UITableView managed by an NSFetchedResultsController, class is called CustomersViewController. When I select a customer, a new view controller CustomerDetailViewController is loaded which displays their details and then a list of radiators related to them in another UITableView managed by an NSFetchedResultsController. The only editing I need on the tables is Deletion and this works fine in both tables managed by NSFetchedResultsController.
I want to be able to edit the customers details, so I have an edit button in the NavigationBar that segues to EditCustomerViewController from CustomerDetailViewController. As with previous segues the managedObjectContext and the managedObject (the selected customer) is passed through successfully and I can access all the objects values in the EditCustomerViewController, what I can't seem to do is edit them without getting these errors:
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)
From this error I am guessing the issue lies with the NSFetchedResultsController not liking me changing the value in the EditCustomerViewController two viewcontrollers ahead of where it was instantiated. Given that there is no table in this view controller I haven't set it up.
The code for the three viewcontrollers in question are:
Code for 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()
}
}
and 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
}
}
}
}
and
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.
}
*/
}
The application keeps running until I navigate back to the CustomersViewController when it finally crashes and I can briefly see that the customer name has changed in the list - it hasn't changed in the CustomerDetailViewController however.
Any help would be great, apologies for any lack of "swifty-ness" (I read that's a thing) - this is my first larger app in swift and iOS so I'm still learning as I go.

Resources