Cell formats incorrectly when inserting row - ios

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.

Related

Why TableView does not reload data after popViewController?

In my app I have a ViewController with a collectionView and a tableView.
My CollectionView shows the dates and the tableView shows the data in that date.
I need to create a date before create the other datas, so when I type in the date it shows up a alert and them we go to other view to Create a new data.
The problem is that when I save a new data and I use a popViewController to go back my previous view my tableView does not reload data.
I can see the saved data when I back the view again and so I return to the view
I've tried to use the myTableView.reloadData() in ViewWillAppear, but it didn't work.
How can I reload myTableView as soon as return the view?
Here is a link to the app video.
Here is the app working
Here is my IBAction to save data:
#IBAction func btSaveNewMeasures(_ sender: UIBarButtonItem) {
let allMeasures = NSEntityDescription.entity(forEntityName: "AllMeasures", in: context)
for textField in tfMeasuresValues {
let newMeasure = NSManagedObject(entity: allMeasures!, insertInto: context)
if let valueTxt = textField.text {
let value = convertNumber.convertToDouble(valueTxt)
if value != 0 {
switch textField.tag {
case 1: ///Body Measures
//Peso
let type = MeasuresType.bodyMeasures.rawValue
let index = NSNumber(value: textField.tag).int64Value
self.saveNewMessure(newMeasure, type: type, title: "Peso", value: value, index: index)
case 2:
//Altura
let type = MeasuresType.bodyMeasures.rawValue
let index = NSNumber(value: textField.tag).int64Value
self.saveNewMessure(newMeasure, type: type, title: "Altura", value: value, index: index)
case 3:
//%Gordura
let type = MeasuresType.bodyMeasures.rawValue
let index = NSNumber(value: textField.tag).int64Value
self.saveNewMessure(newMeasure, type: type, title: "% Gordura", value: value, index: index)
default:
break
}
}
}
}
do {
try context.save()
guard let navigation = navigationController else {return}
navigation.popViewController(animated: true)
self.delegate?.didFinishUpdates(finished: true)
} catch let error {
print("Error Saving Measures Values: \(error)")
}
}
}
This is my cellForRowAt:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: MeasureCell.identifier, for: indexPath) as! MeasureCell
let measures = fetchedRCAllMeasuresByDay.object(at: indexPath)
cell.setupMeasureCell(measure: measures)
return cell
}
I've checked if myTableView.reloadData() has been called in viewWillApperar and I confirmed that.
Here are my methods setupTableView and setupCollectionView in my class MeasureController:
private func setupTableView() {
myTableView.backgroundColor = Theme.background
myTableView.separatorStyle = .none
myTableView.tableFooterView = UIView()
myTableView.delegate = self
myTableView.dataSource = self
myTableView.rowHeight = UITableView.automaticDimension
myTableView.estimatedRowHeight = 80
myTableView.register(MeasureCell.nib, forCellReuseIdentifier: MeasureCell.identifier)
}
private func setupCollectionView() {
myCollectionView.backgroundColor = Theme.background
myCollectionView.delegate = self
myCollectionView.dataSource = self
myCollectionView.register(UINib(nibName: "DateCollectionCell", bundle: nil), forCellWithReuseIdentifier: cellCollectionViewID)
//Set Collection View Cell Aligment
let alignedFlowLayout = AlignedCollectionViewFlowLayout(horizontalAlignment: .right, verticalAlignment: .top)
alignedFlowLayout.scrollDirection = .horizontal
myCollectionView.collectionViewLayout = alignedFlowLayout
}
==============================================
This is my NSFetechedResultsControllerDelegate fixed:
extension MeasureController: NSFetchedResultsControllerDelegate {
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
switch type {
case .insert:
myTableView.insertSections(IndexSet(integer: sectionIndex), with: .fade)
case .delete:
myTableView.deleteSections(IndexSet(integer: sectionIndex), with: .fade)
case .move:
break
case .update:
myTableView.reloadSections(IndexSet(integer: sectionIndex), with: .fade)
#unknown default:
fatalError()
}
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
myTableView.reloadData()
myCollectionView.reloadData()
}
//Allow to show full section name in header.
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, sectionIndexTitleForSectionName sectionName: String) -> String? {
return sectionName
}
}
And here is the link for the video with the new app behaviour, after fixed the NSFRCD.
New App Behaviour
I found that cellForRowAt is not called after new data is added as soon as old data is deleted. Although numberOfSections has been called, the value is zero. But if you go back to the previous view and re-enter MeasureView, numberOfSections will have a correct value, in other words, numberOfSections will be nonzero.
I've tried to use delegates and callbacks to reload myTableView but I didn't success.

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.

Rows in Sections count not updating in TableView

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!

Passing core data from selected cell in tableview with certain attributes to another VC

So basically what I am trying to do is:
I have in my tableView a Title and SubTitle loaded from core data. When I select the cell, I want attributes that are not shown in that cell but stores in same entity to be passed to 3 different UITextField in my ViewController.
I have my prepareForSegue set up and ready, but I am missing what and how to send those attributes.
This is the code from my tableView
Updated Code with NSFetchedResultsController
class LedData: UIViewController, UITableViewDataSource, UITableViewDelegate, NSFetchedResultsControllerDelegate {
let ReuseIdentifierCell = "CellData"
// MARK: - IBOutlet
#IBOutlet var tableView: UITableView!
// MARK: - Variables
var managedObjectContext: NSManagedObjectContext!
lazy var fetchedResultsController: NSFetchedResultsController = {
// Initialize Fetch Request
let fetchRequest = NSFetchRequest(entityName: "Ledinfo")
// Add Sort Descriptors
let sortDescriptor = NSSortDescriptor(key: "manufactor", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
// Initialize Fetched Results Controller
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.managedObjectContext, sectionNameKeyPath: nil, cacheName: nil)
// Configure Fetched Results Controller
fetchedResultsController.delegate = self
return fetchedResultsController
}()
// MARK: - VC Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
do {
try self.fetchedResultsController.performFetch()
} catch {
let fetchError = error as NSError
print("\(fetchError), \(fetchError.userInfo)")
}
}
// MARK: -
// MARK: Table View Data Source Methods
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 sectionInfo = sections[section]
return sectionInfo.numberOfObjects
}
return 0
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(ReuseIdentifierCell, forIndexPath: indexPath) as! CellData
// Configure Table View Cell
configureCell(cell, atIndexPath: indexPath)
return cell
}
func configureCell(cell: CellData, atIndexPath indexPath: NSIndexPath) {
// Fetch Record
let record = fetchedResultsController.objectAtIndexPath(indexPath)
// Update Cell
if let manufactorer = record.valueForKey("manufactor") as? String {
cell.makerName.text = manufactorer
}
if let model = record.valueForKey("model") as? String {
cell.modelName.text = model
}
}
// MARK: -
// MARK: Table View Delegate Methods
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
// MARK: -
// MARK: Fetched Results Controller Delegate Methods
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 .Insert:
if let indexPath = newIndexPath {
tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
break;
case .Delete:
if let indexPath = indexPath {
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
break;
case .Update:
if let indexPath = indexPath {
let cell = tableView.cellForRowAtIndexPath(indexPath) as! CellData
configureCell(cell, atIndexPath: indexPath)
}
break;
case .Move:
if let indexPath = indexPath {
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
if let newIndexPath = newIndexPath {
tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Fade)
}
break;
}
}
}
This si what I have in my viewController that should receive.
var viaSegue1:String!
var viaSegue2:String!
var viaSegue3:String!
and
override func viewDidLoad()
{
super.viewDidLoad()
panelWidthTextField.text = viaSegue1
panelHightTextField.text = viaSegue2
panelPitchTextField.text = viaSegue3
}
Hope someone can help with this.
What you should do is use an NSFetchedResultsController to manage the data for your table.
A fetched results controller will save you a lot of inconvenience from having to manage your own array(s) of results from a fetch request.
When it's time to pass the information to your destination view controller, you can retrieve the other attributes right from the model object.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showDetail" {
if let viewController = segue.destinationViewController as? ViewController {
if let indexPath = tableView.indexPathForSelectedRow {
let record = fetchedResultsController.objectAtIndexPath(indexPath) as! NSManagedObject // cast this as the type of your entity
viewController.panelWidth = record.panelWidth
viewController.panelHeight = record.panelHeight
viewController.panelPitch = record.panelPitch
}
}
}
}
Alternately, you could inject the managed object context and object's objectID, then fetch that particular object in your destination view controller.
Also, you make it appear like you're dealing with arrays of two different entities.
var manufactorer = [NSManagedObject]()
var model = [NSManagedObject]()
You should strongly type cast everything, so Swift knows exactly what type of managed object is in an array or set. If you're dealing with an array of Ledinfo types, you should declare it as such.
As an aside, you may want to use more descriptive names than viaSegue1, and ViewController. This makes your code easier to read, understand, and maintain.

Resources