recently my app got rejected by Apple stating a non-compliance to 2.10 of the apple guidelines.
----- 2.10 -----
We found that your app crashed on iPad, which doesn't meet the requirements for running on iPad, as required by the App Store Review Guidelines.
Apple has sent me the crash logs for the app but I have no idea how to interpret them. The crash log is as such:
EDIT:
I have managed to symbolicate the crash log, here it is:
The Symbolicated Crash Log
here's the source code to the specific function causing the error:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Return the number of rows in the section.
if let numberOfFetchedObjects = fetchedResultsController.fetchedObjects?.count {
return numberOfFetchedObjects
}
else {return 0}
}
heres the rest of the code on the view controller related to the tableView and the fetchedResultsController:
override func viewDidLoad() {
super.viewDidLoad()
setupFetchedResultsControllerAndDelegate()
fetchResults()
self.adBanner.delegate = self
self.goalTableView.allowsSelectionDuringEditing = true
self.goalTableView.rowHeight = CGFloat(90.0)
//check if theres a local notification payload
let appDelegate = UIApplication.sharedApplication().delegate! as AppDelegate
if let notification = appDelegate.localNotificationToPass {
self.performFollowUpActionForNotification(notification)
appDelegate.localNotificationToPass = nil
}
//check if theres a need to present rate app alert
if appDelegate.goalPageShouldShowAlert == true {
self.showRateAppAlert()
}
}
//MARK: - Fetched Results Controller Set Up & Helper Methods
func setupFetchedResultsControllerAndDelegate() {
//initialize fetch request
let fetchRequest:NSFetchRequest = NSFetchRequest(entityName: "Goal")
//initialize predicate
let predicate:NSPredicate = NSPredicate(format:"isDone == %#", false)!
fetchRequest.predicate = predicate
//set sort descriptor
let sortDescriptor:NSSortDescriptor = NSSortDescriptor(key: "dateCreated", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
//set up fetched results controller variable
fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
fetchedResultsController.delegate = self
}
func fetchResults() {
var error = NSErrorPointer()
if !fetchedResultsController.performFetch(error) {
//MARK: replace the print line function below to an alert for consumers later on
println("Could not fetch results \n with error: \(error)")
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:GoalCell! = tableView.dequeueReusableCellWithIdentifier("goalCell", forIndexPath: indexPath) as GoalCell
configureCell(cell, atIndexPath: indexPath, forViewController: self)
return cell
}
func configureCell(cell:GoalCell, atIndexPath indexPath: NSIndexPath, forViewController viewController:UIViewController){
var fetchedGoals = self.fetchedResultsController.fetchedObjects as [Goal]
let cellGoal = fetchedGoals[indexPath.row]
cell.goalNameLabel.text = cellGoal.name
cell.goalDatelineLabel.text = Goal.convertGoalDueDateToString(cellGoal)
cell.parentViewController = self
cell.clipsToBounds = true
}
// Override to support conditional editing of the table view.
func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
// Return NO if you do not want the specified item to be editable.
return true
}
func tableView(tableView: UITableView, editingStyleForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCellEditingStyle {
return UITableViewCellEditingStyle.Delete
}
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
let goal:Goal = fetchedResultsController.fetchedObjects![indexPath.row] as Goal
self.context.deleteObject(goal)
self.cancelNotificationForGoal(goal)
var error = NSErrorPointer()
self.context.save(error)
}
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if self.goalTableView.editing == true {
self.performSegueWithIdentifier("addOrEditGoal", sender: self)
}
else {
self.performSegueWithIdentifier("showGoalDetailView", sender: self)
}
}
func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
cell.backgroundColor = UIColor.clearColor()
}
func tableView(tableView: UITableView, willBeginEditingRowAtIndexPath indexPath: NSIndexPath) {
self.setEditingForVC()
}
func tableView(tableView: UITableView, didEndEditingRowAtIndexPath indexPath: NSIndexPath) {
if self.goalTableView.editing == false {
self.changeDoneButtonToEdit()
}
}
//MARK: - Fetched Results Controller Delegate Protocol Methods
func controllerWillChangeContent(controller: NSFetchedResultsController) {
self.goalTableView.beginUpdates()
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
self.showOrHideEmptyTableViewLabel()
self.goalTableView.endUpdates()
}
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type {
case NSFetchedResultsChangeType.Insert :
self.goalTableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
case NSFetchedResultsChangeType.Delete :
self.goalTableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
case NSFetchedResultsChangeType.Update:
self.configureCell((self.goalTableView.cellForRowAtIndexPath(indexPath!) as GoalCell), atIndexPath: indexPath!, forViewController: self)
case NSFetchedResultsChangeType.Move:
self.goalTableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
self.goalTableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
}
}
//MARK: - ADBannerViewDelegate
func bannerViewActionShouldBegin(banner: ADBannerView!, willLeaveApplication willLeave: Bool) -> Bool {
//if will leave is true, it means the ad might take the user to another app or window.
if willLeave == true {
let error = NSErrorPointer()
self.context.save(error)
return true
}
else {return true}
}
func bannerView(banner: ADBannerView!, didFailToReceiveAdWithError error: NSError!) {
banner.hidden = true
self.tableViewBottomConstraint.constant = 0
self.goalTableView.layoutIfNeeded()
}
func bannerViewDidLoadAd(banner: ADBannerView!) {
banner.hidden = false
self.tableViewBottomConstraint.constant = banner.frame.height
self.goalTableView.layoutIfNeeded()
}
Related
I'm getting the following error when I delete a cell from the table view:
Invalid update: invalid number of sections. The number of sections
contained in the table view after the update (0) must be equal to the
number of sections contained in the table view before the update (1),
plus or minus the number of sections inserted or deleted (0 inserted,
0 deleted)
I'm using the NSFetchedResultsControllerDelegate to respond to the changes in the number of section. I'm retrieving the data from a plist file in Core Data and each addition of the table view cell increases the number of sections accordingly, so 3 cells = 3 sections.
override func numberOfSections(in tableView: UITableView) -> Int {
return fetchedResultsController.sections?.count ?? 0
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let sectionInfo = fetchedResultsController.sections![section]
return sectionInfo.numberOfObjects
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let goal = fetchedResultsController.object(at: indexPath)
cell.textLabel!.text = goal.title
return cell
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let goal = fetchedResultsController.object(at: indexPath)
self.context.delete(goal)
self.saveContext()
}
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch type {
case .delete:
tableView.deleteRows(at: [indexPath!], with: .automatic)
default:
break
}
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
let section = IndexSet(integer: sectionIndex)
switch type {
case .delete:
tableView.deleteSections(section, with: .automatic)
default:
break
}
}
context is this:
var context : NSManagedObjectContext {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
return appDelegate.persistentContainer.viewContext
}
How I load the saved Core Data onto FRC:
func loadSavedData() {
if fetchedResultsController == nil {
let request = Goal.createFetchRequest()
let sort = NSSortDescriptor(key: "date", ascending: false)
request.sortDescriptors = [sort]
request.fetchBatchSize = 20
fetchedResultsController = NSFetchedResultsController(fetchRequest: request, managedObjectContext: self.context, sectionNameKeyPath: "title", cacheName: nil)
fetchedResultsController.delegate = self
}
fetchedResultsController.fetchRequest.predicate = goalPredicate
do {
try fetchedResultsController.performFetch()
tableView.reloadData()
} catch {
print("Fetch failed")
}
}
try calling beginUpdates and endUpdates in these delegate apis also.
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates
}
if you still face issues then in this delegate function, try to call like this:
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.performBatchUpdates {
tableView.deleteRows(at: [indexPath], with: .automatic)
tableView.deleteSections(section, with: .automatic)
} completion: { (status) in
}
}
I have a pretty straight forward UITableViewController /NSFetchedResultsController case here. It's from Xcode Master-Detail App sample code, So easy to reproduce.
I have a CoreData Model with 1 Entity with 1 String Attribute. It's displayed in a UITableViewController. I use .subtitle system cell type.
By selecting a row, I simply update the String Attribute.
So my problem is, when I add just enough rows for the tableview to scroll (10-11 rows on a iPhone 5s with navbar), and I scroll down, and select any row, the tableview bounces up and down.
If there is less rows (less than 10 rows), or more rows (12 rows and more), the behaviour is normal.
So It seems to be at the limit of the scroll view the problem happens.
If I don't use beginUpdates()/endUpdates(), the problem goes away, but I loose their advantages.
Here is a video link of what happens
class TableViewController: UITableViewController, NSFetchedResultsControllerDelegate {
var managedObjectContext: NSManagedObjectContext? = nil
#objc func insertNewObject(_ sender: Any) {
let context = self.fetchedResultsController.managedObjectContext
let newEvent = Event(context: context)
newEvent.aString = "a String"
try? context.save()
}
override func viewDidLoad() {
super.viewDidLoad()
let addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(insertNewObject(_:)))
navigationItem.rightBarButtonItem = addButton
}
override func numberOfSections(in tableView: UITableView) -> Int {
return fetchedResultsController.sections?.count ?? 0
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let sectionInfo = fetchedResultsController.sections![section]
return sectionInfo.numberOfObjects
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let event = fetchedResultsController.object(at: indexPath)
configureCell(cell, withEvent: event)
return cell
}
func configureCell(_ cell: UITableViewCell, withEvent event: Event) {
cell.textLabel?.text = event.aString
cell.detailTextLabel?.text = event.aString
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let event: Event = self.fetchedResultsController.object(at: indexPath)
event.aString = event.aString! + ""
}
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 context = fetchedResultsController.managedObjectContext
context.delete(fetchedResultsController.object(at: indexPath))
try? context.save()
}
}
var fetchedResultsController: NSFetchedResultsController<Event> {
if _fetchedResultsController != nil {
return _fetchedResultsController!
}
let fetchRequest: NSFetchRequest<Event> = Event.fetchRequest()
fetchRequest.fetchBatchSize = 20
let sortDescriptor = NSSortDescriptor(keyPath: \Event.aString, ascending: false)
fetchRequest.sortDescriptors = [sortDescriptor]
let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.managedObjectContext!, sectionNameKeyPath: nil, cacheName: nil)
aFetchedResultsController.delegate = self
_fetchedResultsController = aFetchedResultsController
try? _fetchedResultsController!.performFetch()
return _fetchedResultsController!
}
var _fetchedResultsController: NSFetchedResultsController<Event>? = nil
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
switch type {
case .insert:
tableView.insertSections(IndexSet(integer: sectionIndex), with: .automatic)
case .delete:
tableView.deleteSections(IndexSet(integer: sectionIndex), with: .automatic)
default:
return
}
}
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: .automatic)
case .delete:
tableView.deleteRows(at: [indexPath!], with: .automatic)
case .update:
configureCell(tableView.cellForRow(at: indexPath!)!, withEvent: anObject as! Event)
case .move:
configureCell(tableView.cellForRow(at: indexPath!)!, withEvent: anObject as! Event)
tableView.moveRow(at: indexPath!, to: newIndexPath!)
}
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
}
}
I was running into the same problem, and I found that implementing the estimatedHeightFor... methods fixed the issue for me. The issue seems to stem from using automatic cell heights in the table instead of explicitly defined heights per row.
The table I'm using has both rows and section header/footers, so I needed to define estimated heights for both rows and headers, and this solved the strange bouncing animation during batch updates.
Note that returning 0 for section header heights will use the table view's default value which is likely UITableViewAutomaticDimension, so return a positive number for the estimated heights.
My code in Obj-C:
-(CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath{
return 44; // anything except 0 or UITableViewAutomaticDimension
}
-(CGFloat)tableView:(UITableView *)tableView estimatedHeightForHeaderInSection:(NSInteger)section{
return 18; // anything except 0 or UITableViewAutomaticDimension
}
-(CGFloat)tableView:(UITableView *)tableView estimatedHeightForFooterInSection:(NSInteger)section{
return 18; // anything except 0 or UITableViewAutomaticDimension
}
I'm a beginner in IOS development in swift. The problem I am facing is: I am building an app using CoreData and the app contains table view and table cell. I can't really explain because of my lack of knowledge so I'm sharing screenshots. I have seen other Questions asked, none of them solved my error. and I have also made a function for context in AppDelegate which is
#available(iOS 10.0, *)
let ad = UIApplication.shared.delegate as! AppDelegate
#available(iOS 10.0, *)
let context = ad.persistentContainer.viewContext
my code for VC is
import UIKit
import CoreData
class MainVC: UIViewController, UITableViewDelegate, UITableViewDataSource, NSFetchedResultsControllerDelegate {
#IBOutlet weak var tableViewmain: UITableView!
#IBOutlet weak var topSegment: UISegmentedControl!
var fetchResultControll: NSFetchedResultsController<Items>!
override func viewDidLoad() {
super.viewDidLoad()
tableViewmain.delegate = self
tableViewmain.dataSource = self
doFetch()
}
func configureCell (cell: ItemsCell, indexPath: IndexPath) {
let item = fetchResultControll.object(at: indexPath) // remember as here
cell.confugringCell(item: item)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableViewmain.dequeueReusableCell(withIdentifier: "ItemsCell", for: indexPath) as! ItemsCell
configureCell(cell: cell, indexPath: indexPath)
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let sections = fetchResultControll.sections{
let sectionInfo = sections[section]
return sectionInfo.numberOfObjects
}
return 0
}
func numberOfSections(in tableView: UITableView) -> Int {
if let allSections = fetchResultControll.sections {
return allSections.count
}
return 0
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 150
}
func doFetch() {
let fetchRequest: NSFetchRequest<Items> = Items.fetchRequest()
let dateSrot = NSSortDescriptor(key: "created", ascending: false)
fetchRequest.sortDescriptors = [dateSrot]
if #available(iOS 10.0, *) {
let controller = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
do {
try controller.performFetch()
}
catch {
let err = error as NSError
print("\(err)")
}
} else {
// Fallback on earlier versions
}
}
//controler willchnge
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableViewmain.beginUpdates()
}
//controlerdidchnge
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableViewmain.endUpdates()
}
//controlerdidchangeanobject
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch(type) {
case .insert:
if let indexpath = newIndexPath {
tableViewmain.insertRows(at: [indexpath], with: .fade)
}
break
case .delete:
if let indexpath = indexPath {
tableViewmain.deleteRows(at: [indexpath], with: .fade)
}
break
case .update:
if let indexpath = indexPath {
let cell = tableViewmain.cellForRow(at: indexpath) as! ItemsCell
configureCell(cell: cell, indexPath: indexpath) // as used here
}
break
case .move:
if let indexpath = indexPath {
tableViewmain.deleteRows(at: [indexpath], with: .fade)
}
if let indexpath = newIndexPath {
tableViewmain.insertRows(at: [indexpath], with: .fade)
}
break
}
}
I hope you understand me.. Any Help would be highly appreciated
Replace following line with your .
var fetchResultControll: NSFetchedResultsController<Items>?
Ok I am brand new to this and am a bit overwhelmed going through many tutorials and articles. And spent a few hours sorting through similar issues with no luck in fixing my own. I have a "AddSiteVC" to allow the user to add or delete Items that are put into CoreData and then displayed in a TableView on my "MainVC". My problem is when I press save or delete and get dismissed back to my MainVC onBtnClick the TableView doesn't update until I leave the MainVC and then come back. I don't know what I'm doing wrong but can't seem to find anything that fixes this... I don't know where my problem is so I'll include most of my MainVC code for reference.
Any help would be greatly appreciated!
Thanks!
import UIKit
import CoreData
class SitesViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, NSFetchedResultsControllerDelegate {
#IBOutlet weak var tableView: UITableView!
var controller: NSFetchedResultsController<Sites>!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
attemptFetch()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SitesCell", for: indexPath) as! SitesCell
configureCell(cell: cell, indexPath: indexPath as NSIndexPath)
return UITableViewCell()
}
func configureCell(cell: SitesCell, indexPath: NSIndexPath) {
let sites = controller.object(at: indexPath as IndexPath)
cell.configureCell(sites: sites)
cell.accessoryType = .detailButton
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "AddSiteViewController" {
if let destination = segue.destination as? AddSiteViewController {
if let site = sender as? Sites {
destination.siteToEdit = site
}
}
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let sections = controller.sections {
let sectionInfo = sections[section]
return sectionInfo.numberOfObjects
}
return 0
}
func numberOfSections(in tableView: UITableView) -> Int {
if let sections = controller.sections {
return sections.count
}
return 0
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 75
}
func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
if let objs = controller.fetchedObjects, objs.count > 0 {
let site = objs[indexPath.row]
performSegue(withIdentifier: "AddSiteViewController", sender: site)
}
}
func attemptFetch() {
let fetchRequest: NSFetchRequest<Sites> = Sites.fetchRequest()
let alphebaticalSort = NSSortDescriptor(key: "name", ascending: true)
fetchRequest.sortDescriptors = [alphebaticalSort]
let controller = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
controller.delegate = self
self.controller = controller
do {
try controller.performFetch()
} catch {
let error = error as NSError
print("\(error)")
}
}
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch (type) {
case.insert:
if let indexPath = newIndexPath {
tableView.insertRows(at: [indexPath], with: .fade)
}
break
case.delete:
if let indexPath = indexPath {
tableView.deleteRows(at: [indexPath], with: .fade)
}
break
case.update:
if let indexPath = indexPath {
let cell = tableView.cellForRow(at: indexPath) as! SitesCell
configureCell(cell: cell, indexPath: indexPath as NSIndexPath)
}
break
case.move:
if let indexPath = indexPath {
tableView.deleteRows(at: [indexPath], with: .fade)
}
if let indexPath = newIndexPath {
tableView.insertRows(at: [indexPath], with: .fade)
}
break
}
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
}
}
Please make following change in the code . Because viewDidLoad will be called when viewcontroller is loaded . But as per your requirement you adding something in Modal page. So you need move the code to viewWillAppear
import UIKit
import CoreData
class SitesViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, NSFetchedResultsControllerDelegate {
#IBOutlet weak var tableView: UITableView!
var controller: NSFetchedResultsController<Sites>!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
attemptFetch()
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SitesCell", for: indexPath) as! SitesCell
configureCell(cell: cell, indexPath: indexPath as NSIndexPath)
return cell }
I am tryin to use NSFetchedResultsController in a UITableView.. But for some reason the NSFetchedResultsControllerDelegate are not getting fired when I add a new entry. Here is the complete code
import UIKit
import CoreData
let coreDataStack = CoreDataStack()
class CDListViewController: UITableViewController, NSFetchedResultsControllerDelegate {
var fetchedResultsController: NSFetchedResultsController {
if _fetchedResultsController != nil {
return _fetchedResultsController!
}
let fRequest = fetchRequest()
let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fRequest, managedObjectContext: coreDataStack.managedObjectContext!, sectionNameKeyPath: nil, cacheName: nil)
aFetchedResultsController.delegate = self
_fetchedResultsController = aFetchedResultsController
var error: NSError? = nil
if !_fetchedResultsController!.performFetch(&error) {
}
println(error)
return _fetchedResultsController!
}
var _fetchedResultsController: NSFetchedResultsController? = nil
func fetchRequest() -> NSFetchRequest {
let fetchRequest = NSFetchRequest(entityName: "Menu")
// Set the batch size to a suitable number.
fetchRequest.fetchBatchSize = 2
// Edit the sort key as appropriate.
let sortDescriptor = NSSortDescriptor(key: "createdAt", ascending: false)
fetchRequest.sortDescriptors = [sortDescriptor]
return fetchRequest
}
override func viewDidLoad() {
super.viewDidLoad()
fetchedResultsController.delegate = self
print(fetchedResultsController)
// fetchedResultsController?.performFetch(nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func tableView(tableView: UITableView, editingStyleForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCellEditingStyle {
return UITableViewCellEditingStyle.Delete
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let menus = fetchedResultsController.fetchedObjects as [Menu]
return menus.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
let curr = fetchedResultsController.objectAtIndexPath(indexPath) as Menu
// Configure the cell...
cell.textLabel?.text = curr.text
return cell
}
// Override to support conditional editing of the table view.
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
// Return NO if you do not want the specified item to be editable.
return true
}
// Override to support editing the table view.
func controllerWillChangeContent(controller: NSFetchedResultsController) {
println("Coming in here")
self.tableView.beginUpdates()
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
self.tableView.endUpdates()
}
// Override to support editing the table view.
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
let entry = self.fetchedResultsController.objectAtIndexPath(indexPath) as Menu
coreDataStack.managedObjectContext?.deleteObject(entry)
coreDataStack.saveContext()
}
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch(type) {
case NSFetchedResultsChangeType.Insert : self.tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: UITableViewRowAnimation.Automatic)
break
case NSFetchedResultsChangeType.Delete : self.tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Automatic)
break
case NSFetchedResultsChangeType.Update : self.tableView.reloadRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Automatic)
default:
println("Nothing")
}
}
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
switch(type) {
case NSFetchedResultsChangeType.Insert : self.tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: UITableViewRowAnimation.Automatic)
break
case NSFetchedResultsChangeType.Delete : self.tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: UITableViewRowAnimation.Automatic)
break
default:
println("Nothing")
}
}
}
When I add an entry to the context and save the content neither controllerWillChangeContent nor controllerDidChangeContent is being called. I made sure that the context is saved because if I restart the app I can see the recently added entry.
Can anyone find any issue with the code written above?
I noticed you set the delegate twice. Once inside the computed property and once in the viewDidLoad. I think your issue may arise from the fact that you set it for aFetchedResultsController in the computed property first, and thus, the delegate methods are called when aFetchedResultsController's delegate methods are called (which they are not). Deleting the delegation assignment (aFetchedResultsController.delegate = self) in your computed property should resolve this issue.