Invalid update: invalid number of rows in section 0 with NSFetchedResultsController - ios

I have 2 ViewControllers:
VC1 populates its tableView based on CoreData attribute isPicked, which is bool and show only items with true state. VC2 is a second Modal (not Full Screen) View Controller which allow user to change the state of isPicked attribute: check and uncheck item (make it true or false). The idea is user picks needed items end dismiss the VC2 and the items should appear in VC1.
I have implemented NSFetchedResultsController to both VC1 and VC2. And as soon as I press on first item (i.e. change isPicked state to true) I receive the error from VC1:
[error] fault: 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 (1), 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).
Here is how I change a state of item in VC2 (true\false):
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let currencyArray = fetchedResultsController.fetchedObjects else { return }
let cell = tableView.cellForRow(at: indexPath) as! PickCurrencyTableViewCell
for currency in currencyArray {
if currency.shortName == cell.shortName.text {
currency.isPicked = !currency.isPicked
coreDataManager.save()
}
}
}
Here is my VC1 NSFetchedResultsController implementation:
override func viewDidLoad() {
super.viewDidLoad()
setupFetchedResultsController()
}
func setupFetchedResultsController() {
let predicate = NSPredicate(format: "isForConverter == YES")
fetchedResultsController = createCurrencyFetchedResultsController(and: predicate)
fetchedResultsController.delegate = self
try? fetchedResultsController.performFetch()
}
func createCurrencyFetchedResultsController(with request: NSFetchRequest<Currency> = Currency.fetchRequest(), and predicate: NSPredicate? = nil, sortDescriptor: [NSSortDescriptor] = [NSSortDescriptor(key: "shortName", ascending: true)]) -> NSFetchedResultsController<Currency> {
request.predicate = predicate
request.sortDescriptors = sortDescriptor
return NSFetchedResultsController(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
}
Here is my VC1 NSFetchedResultsController delegate:
extension VC1: NSFetchedResultsControllerDelegate {
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates()
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
if let indexPath = indexPath, let newIndexPath = newIndexPath {
switch type {
case .update:
tableView.reloadRows(at: [indexPath], with: .none)
case .move:
tableView.moveRow(at: indexPath, to: newIndexPath)
case .delete:
tableView.deleteRows(at: [indexPath], with: .none)
case .insert:
tableView.insertRows(at: [indexPath], with: .none)
default:
tableView.reloadData()
}
}
}
}
When I reload the app, picked item shows itself in VC1 (which caused crash). But every change in VC2 crashes the app again with the same error. I don't have any methods to delete items in VC1 or so. I need that VC1 tableView just show or hide items according to isPicked state made from VC2.
What I missed in my code?
UPDATE: my VC1 TableView methods
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let currency = fetchedResultsController.sections![section]
return currency.numberOfObjects
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "converterCell", for: indexPath) as! ConverterTableViewCell
let currency = fetchedResultsController.object(at: indexPath)
cell.shortName.text = currency.shortName
cell.fullName.text = currency.fullName
return cell
}

Doc states:
controller(_:didChange:at:for:newIndexPath:)
indexPath
The index path of the changed object (this value is nil for
insertions).
So, you should change the code like this:
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch type {
case .update:
if let indexPath = indexPath {
tableView.reloadRows(at: [indexPath], with: .none)
}
case .move:
if let indexPath = indexPath {
tableView.moveRow(at: indexPath, to: newIndexPath)
}
case .delete:
if let indexPath = indexPath {
tableView.deleteRows(at: [indexPath], with: .none)
tableView.reloadData() // << May be needed ?
}
case .insert:
if let newIndexPath = newIndexPath {
tableView.insertRows(at: [newIndexPath], with: .none)
tableView.reloadData() // << May be needed ?
}
default:
tableView.reloadData()
}
}
}

Related

CoreData with tableView Sections

I have CoreData Model:
And I try use fetchResultsController to display sections and rows. Sections should be from Category entity and rows should be from Items entity. How I can achieve this?
I use code:
override func numberOfSections(in tableView: UITableView) -> Int {
return fetchResultsController?.sections?.count ?? 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let sectionInfo = fetchResultsController.sections![section]
return sectionInfo.numberOfObjects
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! ItemsTableViewCell
if let person = fetchResultsController?.object(at: indexPath) {
cell.textLabel?.text = person.name
}
return cell
}
But with this code I get only Category names. But I want to show Items related to special category. What I should change in
if let person = fetchResultsController?.object(at: indexPath) {
cell.textLabel?.text = person.name
}
to display Items for special Category?
My code for configuring fetchResultsController:
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: guard let indexPath = newIndexPath else { break }
tableView.insertRows(at: [indexPath], with: .fade)
case .delete: guard let indexPath = indexPath else { break }
tableView.deleteRows(at: [indexPath], with: .fade)
case .update: guard let indexPath = indexPath else { break }
tableView.reloadRows(at: [indexPath], with: .fade)
default:
tableView.reloadData()
}
categories = controller.fetchedObjects as! [Category]
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
}
And:
var fetchResultsController: NSFetchedResultsController<Category>!
let request: NSFetchRequest<Category> = Category.fetchRequest()
request.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]
fetchResultsController = NSFetchedResultsController<Category>(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
try? fetchResultsController?.performFetch()
fetchResultsController?.delegate = self
tableView.reloadData()
You need to base the frc on the Item entity, and use the sectionNameKeyPath to reference the Category name:
var fetchResultsController: NSFetchedResultsController<Item>!
let request: NSFetchRequest<Item> = Item.fetchRequest()
request.sortDescriptors = [NSSortDescriptor(key: "list.name", ascending: true)]
fetchResultsController = NSFetchedResultsController<Item>(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: “list.name”, cacheName: nil)
try? fetchResultsController?.performFetch()
fetchResultsController?.delegate = self
tableView.reloadData()

NSFetchedResultsController doesn't respond to changes in the number of sections

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
}
}

UITableView unexpectedly bounces with beginUpdates()/endUpdates()/performBatchUpdates()

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
}

Fail on UITableView update

Need your help guys !
I'm going mad on a bug on iOS 9 only.
As you can see below, it is going well on iOS 11
At the beginning, i've got 4 cells with a status "Nouveau" for new.
When I click on a "new" cell, the status change to "En attente" (for "waiting").
On iOS 9, everything is fine until the last cell. When I tap on the last "new" cell, it doesn't move to "waiting" and the "new" section is still there. After this, the table view is not getting updates anymore, it is going back to normal when i change to another viewController and go back to the one containing the table view.
I'm using NSFetchedResultsController. Data is good, good number of sections, good number of elements by section, good title.
At the start 1 sections with title "Nouveau" with 4 objects
At the end, 1 sections with title "En attente" with 4 objects
extension CRDashboardOrdersViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
print( self.fetchResult?.sections?.count ?? 0 )
return self.fetchResult?.sections?.count ?? 0
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print( self.fetchResult?.sections?.get(index: section)?.numberOfObjects ?? 0 )
return self.fetchResult?.sections?.get(index: section)?.numberOfObjects ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: CRDashboardOrderTableViewCell = tableView.dequeueReusableCell(withIdentifier: "CRDashboardOrderTableViewCell") as! CRDashboardOrderTableViewCell
let order: Order = self.fetchResult.object(at: indexPath) as! Order
cell.order = order
if let selectedIndexPath = self.selectedIndexPath, selectedIndexPath == indexPath {
self.tableView.selectRow(at: selectedIndexPath, animated: false, scrollPosition: .none)
}
return cell
}
}
extension CRDashboardOrdersViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.selectedIndexPath = indexPath
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
print( self.fetchResult?.sections?.get(index: section)?.name ?? "???" )
return self.fetchResult?.sections?.get(index: section)?.name ?? "???"
}
}
extension CRDashboardOrdersViewController: NSFetchedResultsControllerDelegate {
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
self.tableView.beginUpdates()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
print(sectionIndex)
print(sectionInfo.indexTitle)
switch type {
case .insert:
self.tableView.insertSections(IndexSet(integer: sectionIndex), with: .fade)
case .delete:
self.tableView.deleteSections(IndexSet(integer: sectionIndex), with: .fade)
default:
break
}
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch type {
case .update:
self.tableView.reloadRows(at: [indexPath!], with: .fade)
if indexPath == self.selectedIndexPath {
self.selectedIndexPath = indexPath
}
break
case .insert:
self.tableView.insertRows(at: [newIndexPath!], with: .fade)
self.selectedIndexPath = nil
break
case .delete:
self.tableView.deleteRows(at: [indexPath!], with: .fade)
self.selectedIndexPath = nil
break
case .move:
self.tableView.moveRow(at: indexPath!, to: newIndexPath!)
if indexPath == self.selectedIndexPath {
self.selectedIndexPath = newIndexPath
}
break
}
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
self.tableView.endUpdates()
}
}
You have made a mistake with your cellForRow: and your FetchedResults delegate.
When using a FetchedResultsController, on .update you should not use reloadRows, this causes problems, instead you should have a method which updates a cells model like so:
func updateCell(atPath indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) as? MyCell {
updateCell(withCell: cell, atIndexPath: indexPath)
}
}
func updateCell(withCell cell: MyCell, atIndexPath indexPath: IndexPath) {
if let MyModel = fetchedResultsController?.object(at: indexPath) {
cell.model = myModel
}
}
Then in your delegate .update you call updateCell(AtIndexPath), to update the cells UI.
You should also use this cell configure method in your cellForRow

Displaying multiple NSFetchedResultsControllers in different sections of same UITableView

good morning.
In my App, I have different Entities and I want to show it in the same TableView.
I know that to do it, I need different NSFetchedResultsController.
When I add an object do the first section, no problem appears, but when I add an object to the second, the problem appears.
Here is the code the I'm using
import UIKit
import CoreData
class MainMenu: UITableViewController, NSFetchedResultsControllerDelegate {
/**********/
/** Vars **/
/**********/
lazy var frcAccount: NSFetchedResultsController = self.AccountFetchedResultController()
lazy var frcCar: NSFetchedResultsController = self.CarFetchedResultsController()
let idxAccNew = NSIndexPath(forRow: 0, inSection: 0)
let idxCarNew = NSIndexPath(forRow: 0, inSection: 1)
let idxConfig = NSIndexPath(forRow: 0, inSection: 2)
/**************/
/** IBOutlet **/
/**************/
/***************/
/** IBActions **/
/***************/
/***************/
/** Functions **/
/***************/
// MARK:- NSFetchedResultsController
func AccountFetchedResultController() -> NSFetchedResultsController {
let fetchRequest = NSFetchRequest(entityName: Account.entityName)
fetchRequest.fetchBatchSize = 10
let sortDescriptor = NSSortDescriptor(key: "id", ascending: false)
fetchRequest.sortDescriptors = [sortDescriptor]
frcAccount = NSFetchedResultsController(fetchRequest: fetchRequest,
managedObjectContext: coreDataStack.context,
sectionNameKeyPath: nil,
cacheName: nil)
frcAccount.delegate = self
do {
try frcAccount.performFetch()
} catch {
print("Erro: \(error)")
abort()
}
return frcAccount
}
func CarFetchedResultsController() -> NSFetchedResultsController {
let fetchRequest = NSFetchRequest(entityName: "Car")
fetchRequest.fetchBatchSize = 10
let sortDescriptor = NSSortDescriptor(key: "plate", ascending: false)
fetchRequest.sortDescriptors = [sortDescriptor]
frcCar = NSFetchedResultsController(fetchRequest: fetchRequest,
managedObjectContext: coreDataStack.context,
sectionNameKeyPath: nil,
cacheName: nil)
frcCar.delegate = self
do {
try frcCar.performFetch()
} catch {
print("Error: \(error)")
abort()
}
return frcCar
}
//MARK:- TableViewDataSource
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 3
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section{
case 0:
return frcAccount.sections![0].numberOfObjects + 1
case 1:
return frcCar.sections![0].numberOfObjects + 1
default:
return 1
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.section == 0 {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell") //as UITableViewCell
cell!.textLabel?.text = "Nova Conta"
return cell!
} else {
let cell = tableView.dequeueReusableCellWithIdentifier("CellAccount") as! VC_AccountListTableViewCell
let idx = NSIndexPath(forRow: indexPath.row - 1, inSection: 0)
let object = frcAccount.objectAtIndexPath(idx) as! Account
cell.useAccountInfo(object)
return cell
}
} else if indexPath.section == 1{
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell")
cell!.textLabel?.text = "Novo Carro"
return cell!
} else {
let cell = tableView.dequeueReusableCellWithIdentifier("CellCar") as! VC_CarListTableViewCell
let idx = NSIndexPath(forRow: indexPath.row - 1, inSection: 0)
let object = frcCar.objectAtIndexPath(idx) as! Car
cell.useCarInfo(object)
cell.btnFillUp.tag = indexPath.row - 1
cell.btnService.tag = indexPath.row - 1
return cell
}
} else {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell")
cell!.textLabel?.text = "Configurações"
return cell!
}
}
// MARK: TableViewDelegate
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if indexPath == idxAccNew || indexPath == idxCarNew || indexPath == idxConfig {
return 44
} else {
if indexPath.section == 0 {
return 80
} else if indexPath.section == 1 {
return 100
} else {
return 0
}
}
}
// MARK:- NSFetchedResultsControllerDelegate
func controllerWillChangeContent(controller:NSFetchedResultsController) {
tableView.beginUpdates()
}
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
switch(type) {
case .Insert:
tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
case .Delete:
tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
default:
return
}
}
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch (type) {
case .Insert:
tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
case .Delete:
tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
case .Move:
tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
case .Update:
tableView.reloadRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
}
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
tableView.reloadData()
tableView.endUpdates()
}
func reloadData() {
tableView.reloadData()
}
}
Is it possible to do this?? I have tried lots of different solutions..
Thanks all
In the tableView datasource methods, you are correctly remapping the tableView's indexPath to the relevant FRC's indexPath, whilst also adjusting for the additional first row, eg:
let idx = NSIndexPath(forRow: indexPath.row - 1, inSection: 0)
(Note that you will have to use similar remapping in other tableView datasource delegate methods, e.g. if you implement didSelectRowAtIndexPath). But you also need to do the reverse mapping in the FRC delegate methods, eg.
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
var tvIdx = NSIndexPath()
var newTvIdx = NSIndexPath()
var tvSection = 0
// for the Car FRC, we need to insert/delete rows in table view section 1:
if (controller == self.frcCar) {
tvSection = 1
}
// adjust the indexPaths (indexPath and newIndexPath)
// to add the extra row, and use the correct tableView section:
if let idx = indexPath {
tvIdx = NSIndexPath(forRow: idx.row + 1, inSection: tvSection)
}
if let newIdx = newIndexPath {
newTvIdx = NSIndexPath(forRow: newIdx.row + 1, inSection: tvSection)
}
switch (type) {
case .Insert:
tableView.insertRowsAtIndexPaths([newTvIdx], withRowAnimation: .Fade)
case .Delete:
tableView.deleteRowsAtIndexPaths([tvIdx], withRowAnimation: .Fade)
case .Move:
tableView.deleteRowsAtIndexPaths([tvIdx], withRowAnimation: .Fade)
tableView.insertRowsAtIndexPaths([newTvIdx], withRowAnimation: .Fade)
case .Update:
tableView.reloadRowsAtIndexPaths([tvIdx], withRowAnimation: .Fade)
}
}
There is then no need to use reloadData in controllerDidChangeContent:
func controllerDidChangeContent(controller: NSFetchedResultsController) {
tableView.endUpdates()
}

Resources