Jason Brady Jason Brady - 7 months ago 26
Swift Question

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.

enter image description here

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.

enter image description here

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

}

Answer

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.