I have a UITableView which populate it cells with a NSFetchedResultsController based on CoreData attribute isForConverter which is Bool and should be true to be displayed. isForConverter state sets in another ViewController.
When I want to delete some cells from the UITableView and after access cells which wasn't deleted I receive the error:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'no object at index 5 in section at index 0'
There is a GIF with the problem: https://cln.sh/M1aI9Z
My code for deleting cells. I don't need to delete it from database, just change it isForConverter from true to false:
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let currency = fetchedResultsController.object(at: indexPath)
currency.isForConverter = false
coreDataManager.save()
}
}
NSFetchedResultsController Setup and delegates:
func setupFetchedResultsController() {
let predicate = NSPredicate(format: "isForConverter == YES")
fetchedResultsController = coreDataManager.createCurrencyFetchedResultsController(with: predicate)
fetchedResultsController.delegate = self
try? fetchedResultsController.performFetch()
}
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?) {
switch type {
case .update:
if let indexPath = indexPath {
tableView.reloadRows(at: [indexPath], with: .none)
}
case .move:
if let indexPath = indexPath, let newIndexPath = newIndexPath {
tableView.moveRow(at: indexPath, to: newIndexPath)
}
case .delete:
if let indexPath = indexPath {
tableView.deleteRows(at: [indexPath], with: .none)
}
case .insert:
if let newIndexPath = newIndexPath {
tableView.insertRows(at: [newIndexPath], with: .none)
}
default:
tableView.reloadData()
}
}
}
I noticed that if I just add tableView.reloadData() to:
tableView(_ tableView: UITableView, commit editingStyle:
UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath)
Then everything works good. But deletion animation is really fast and antsy. Also according to docs I should not use tableView.reloadData() with NSFetchedResultsController...
How to fix that behaviour?
UPDATE:
It seems I found out what the reason of that crash was. This is what my print() tryings gave: SCREENSHOT.
What is a pickedCurrency: this is a global variable of custom type Currency which I created to receive its attribute currentValue (Double, 87.88). I need that value only from the picked to edit cell. After I use that value for calculation at cellForRowAt() and result of the calculation fills all other cells which is not in the edit mode now.
I define pickedCurrency in textFieldDidBeginEditing() because there I receive the exact row of Currency I picked to edit:
func textFieldDidBeginEditing(_ textField: UITextField) {
pickedCurrency = fetchedResultsController.object(at: IndexPath(row: textField.tag, section: 0))
numberFromTextField = 0
textField.textColor = UIColor(named: "BlueColor")
textField.placeholder = "0"
textField.text = ""
}
And then use it's value in cellForRowAt to calculate all other cells values based on pickedCell value and number I put in a textField of activeCell:
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.flag.image = currencyManager.showCurrencyFlag(currency.shortName ?? "notFound")
cell.shortName.text = currency.shortName
cell.fullName.text = currency.fullName
cell.numberTextField.tag = indexPath.row
cell.numberTextField.delegate = self
if let number = numberFromTextField, let pickedCurrency = pickedCurrency {
cell.numberTextField.text = currencyManager.performCalculation(with: number, pickedCurrency, currency)
}
return cell
}
It seems when I delete a lot of cells and then click on random cell to edit it's not updates its IndexPath(row: textField.tag, section: 0)...
Maybe there is a way to receive Currency object I picked for editing in cellForRowAt()?
I am not certain if this will work but it seems too long for it to be a comment so give the below a try.
Initially I told you could use tag to identify a specific view which is good for quick and simple implementations but when rows get moved / deleted as we have it now, it will be very difficult to manage using tags as you have to constantly update them.
For static table views, they are fine but if your rows will change, then more sophisticated patterns like delegate / observer might be better.
Anyways, what I think can help your situation for now in textFieldDidBeginEditing is stop using the tag to get the index path and get the indexpath from what is tapped.
I still think maybe delegate pattern is better but this might work for you:
func textFieldDidBeginEditing(_ textField: UITextField) {
// Get the coordinates of where we tapped in the table
let tapLocation = textField.convert(textField.bounds.origin,
to: tableView)
if let indexPath = self.tableView.indexPathForRow(at: tapLocation)
{
// don't use tag of textfield anymore
pickedCurrency
= fetchedResultsController.object(at: IndexPath(row: indexPath,
section: 0))
numberFromTextField = 0
textField.textColor = UIColor(named: "BlueColor")
textField.placeholder = "0"
textField.text = ""
}
}
Does this help ?
Related
I am trying to find a way that allows me to insert a new cell under a specific section on my tableview. There is a button that is pressed called "add song" and once a user presses on that it should insert a new cell that is built by with a prototype cell. That prototype cell will allow a user to click on it and edit certain information on that cell. I have been trying to code a way to insert the cell below the cell that is currently in that section which is section "3". I'm sure it is something simple that I am messing up since I'm not very use to doing tableviews. Here is my code:
import UIKit
class MultipleSongsTableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func addSong(_ sender: Any) {
insertNewSongCell()
}
func insertNewSongCell() {
let indexPath = IndexPath(row: -1, section: 3)
tableView.beginUpdates()
tableView.insertRows(at: [indexPath], with: .automatic)
tableView.endUpdates()
}
}
extension MultipleSongsTableViewController {
override func numberOfSections(in tableView: UITableView) -> Int {
return 5
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return 1
} else if section == 1 {
return 1
} else if section == 2 {
return 1
} else if section == 3 {
return 1
} else {
return 1
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "trackTitleCell", for: indexPath) as! ReleaseTitleTableViewCell
return cell
} else if indexPath.section == 1 {
let cell = tableView.dequeueReusableCell(withIdentifier: "genreCell", for: indexPath) as! Genre2TableViewCell
return cell
} else if indexPath.section == 2 {
let cell = tableView.dequeueReusableCell(withIdentifier: "TrackListCell", for: indexPath) as! TrackListTableViewCell
return cell
} else if indexPath.section == 3 {
let cell = tableView.dequeueReusableCell(withIdentifier: "TrackListSongCells", for: indexPath) as! TrackListSongsTableViewCell
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "AddSongButtonCell", for: indexPath) as! AddSongTableViewCell
return cell
}
}
}
Also here is a screenshot of how my viewcontroller looks and where I expect the new cell to populate.
I would like the new cell to be inserted after the cell that says "Song Name", I would like the inserted cell to be the same prototype cell that is currently there because the user can click on that cell and fill out information and change the current "Song Name" label to what ever they want.
First of all you need a data source array for the section for example
var songs = [String]()
Then you have to modify numberOfRowsInSection to return the number of songs for section 3. This method can be simplified anyway
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section {
case 3: return songs.count
default: return 1
}
}
Now you can add a new song to the array and insert the row
func insertNewSongCell() {
let insertionIndex = songs.count
songs.append("New Song")
let indexPath = IndexPath(row: insertionIndex, section: 3)
tableView.insertRows(at: [indexPath], with: .automatic)
}
beginUpdates and endUpdates have no effect in this case, you can omit the lines.
Calling UITableView.insertRows(at:with:) will insert cells into your UITableView. You can insert exactly 1 cell by passing an indexPaths argument containing 1 IndexPath:
self.tableView.insertRows(at: [IndexPath(row: 1, section: 3)], with: .automatic)
You may need to also update your UITableViewDataSource to return expected values, e.g. from its tableView(_:numberOfRowsInSection:), to account for the additional row(s). Otherwise, your app will throw an unhandled exception and crash.
I have 4 rows which shows and hide some data below the selected row. Its works fine Most of the time. But when I click second/third/fourth rows multiple times(like 13-14) times and then click on first row then sometime it crashes not always but it crashes most of the time after multiple clicks. I am changing row height as well. Crash only happen when I select first row(case a), after selecting other rows multiple times. Below is my code -
I tried multiple things like calling it between beginUpdate and endUpdate, check is indexPath exist before reloading
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataSource.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: ”cell”, for: indexPath) as! CustomTableViewCell
let model = dataSource[indexPath.row]
cell.model = model
switch model.type {
case .a:
cell.aList = savedCardList
cell.addACallback = {[weak self] (sender) in
self?.moveToAVC()
}
cell.addACallback= {[weak self] (a) in
self?.selectedA = a
self?.updateButton()
}
case .b:
cell.bList = bList
cell.bSelectedCallback = {[weak self] (bInfo) in
self?.selectedB= bInfo
self?.updateButton()
}
case .c:
cell.cList = cList
if let cInfo = selectedC {
cell.selectOtherCButton.setTitle(cInfo.name, for: .normal)
}
cell.selectOtherCCallback = {[weak self] (sender) in
self?.showSelectCVC()
}
cell.CSelectedCallback = {[weak self] (cInfo) in
self?.selectedC = cInfo
self?.updateButton()
}
case .d:
cell.dTextField.text = dString
cell.dChangedCallBack = {[weak self] (textField) in
if let dStr = textField.text {
self?.dString = dStr
}
self?.updateButton()
}
default:
break
}
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let model = dataSource[indexPath.row]
guard model.isSelected == true else {
return 90
}
switch model.type {
case .a:
if aList.count > 0 {
return 350
}
return 140
case .b:
return 200
case .c:
return 270
case .d:
return 130
default:
return 90
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let model = dataSource[indexPath.row]
selectedType = model.type
updateButtonState(false)
updateButton()
if let selectedIndexPath = selectedIndexPath, selectedIndexPath != indexPath {
self.updatemodelSelectedState(false, at: selectedIndexPath)
self.updatemodelSelectedState(true, at: indexPath)
if let _ = tableView.cellForRow(at: indexPath), let _ = tableView.cellForRow(at: selectedIndexPath) {
self.reloadTableViewRows([selectedIndexPath, indexPath])
tableView.scrollToRow(at: indexPath, at: .top, animated: true)
}
}
else if !model.isSelected {
self.updatemodelSelectedState(true, at: indexPath)
if let _ = tableView.cellForRow(at: indexPath) {
self.reloadTableViewRows([indexPath])
}
}
selectedIndexPath = indexPath
}
private func reloadTableViewRows(_ indexPaths: [IndexPath]) {
DispatchQueue.main.async {
self.myTableView.beginUpdates()
self.myTableView.reloadRows(at: indexPaths, with: .automatic)
self.myTableView.endUpdates()
}
}
Xcode console log(I am only getting this one liner) -
libc++abi.dylib: terminating with uncaught exception of type
NSException
It crashes at self.myTableView.endUpdates() this line. If I remove begin and end updates then it crashes at self.myTableView.reloadRows(at: indexPaths, with: .automatic). Also for one of the row I show a popup on a button click and after then if I select my first row, it crashes!
Also I have collection view inside table view rows which I am hiding and showing. Even though its flow is set to horizontal, after multiple clicks once in a while I notice that collection view rows comes in vertical flow and then in next click it gets back to horizontal flow again. Not sure why this is happening but could this be issue?
Please help. I am trying to resolve this issue from last 2-3 hours :(
Try This one i hope it will be work for you:
UIView.setAnimationsEnabled(false)
tableView.beginUpdates()
tableView.reloadRows(at: [indexPath!], with: .none)
tableView.endUpdates()
UIView.setAnimationsEnabled(true)
I have a problem when deleting an item manually using a custom button, when the amount of items (variable of the cell), happening when the user clicks the minus sign at a value of that variable == 1. The cells correctly slide up as the previous one gets deleted, but then when all the cells below go up, the indexes do not change, even though I correctly remove both the cell and the value from my dictionary.
I tried to unable user activity on the screen, but still, that was not the issue as I understand it is a type index out of range error.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
ordersTableView.register(UINib(nibName: "OrdersTableViewCell", bundle: .main), forCellReuseIdentifier: "ordersTableViewCell")
let cell = ordersTableView.dequeueReusableCell(withIdentifier: "ordersTableViewCell", for: indexPath) as! OrdersTableViewCell
let amount = dishesDictionary[indexPath.item]![4] as! Int
cell.dishAmountLabel.text = ("\(String(amount))x")
#objc func decreaseAmount(sender: UIButton){
let index = sender.tag
let indexPath = IndexPath(item: index, section: 0)
if (dishesDictionary[index]![4] as! Int) > 1 {
let numberOfItems = dishesDictionary[index]![4] as! Int
dishesDictionary[index]![4] = numberOfItems - 1
ordersTableView.reloadRows(at: [indexPath], with: .fade)
print(dishesDictionary[index])
print("COUNT: \(dishesDictionary.count)")
}
else if (dishesDictionary[index]![4] as! Int) == 1 {
ordersTableView.beginUpdates()
self.dishesDictionary.removeValue(forKey: index)
self.ordersTableView.deleteRows(at: [indexPath], with: .fade)
ordersTableView.endUpdates()
UIApplication.shared.beginIgnoringInteractionEvents()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.7, execute: {
self.ordersTableView.reloadData()
UIApplication.shared.endIgnoringInteractionEvents()
})
}
else {
return
}
calculateTotalAmount()
}
SWIFT 3 and Firebase - I'm trying to use the UITable inEditing mode to insert cells into firebase and have a firebase listener that updates my arraty. My insertions to fire base are fine, but I keep getting an index out of range when I try to do the insertion with the new add button i created. Has anyone used Firebase with UITable inEditing mode to insert into a table and update firebase.
This is what I looking for:
enter image description here
The problem maybe be how I wrote the cellForAt:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("zzz indexPath index path row: \(indexPath.row)")
print("zzz indexPath name \(myPins[indexPath.row].pinName)")
print("ZZZ myPins Count \(myPins.count)")
let cell = tableView.dequeueReusableCell(withIdentifier: "PinCell", for: indexPath)
if indexPath.row == 0 && isEditing {
//this means we are in editing mode an we to add the cell that will be addPlace
//let indexPathOfFirstRow = IndexPath(item: 0, section: 0)
cell.textLabel?.text = "Add Place"
print("add place was called index.row is : \(indexPath.row)")
} else {
//let cell = tableView.dequeueReusableCell(withIdentifier: "PinCell", for: indexPath)
let pin = myPins[indexPath.row]
cell.textLabel?.text = pin.pinName
}
return cell
}
i get this error message: fatal error: Index out of range
Here is the rest of the table set up code:
//When the use press the editing button it will make the table enditable
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
//if the viw is in editing mode then we will set the table to be in enditing mode.
if isEditing {
//This is adding the addtional cell in for add place
tableView.beginUpdates()
let indexPath = IndexPath(row: 0, section: 0)
tableView.insertRows(at: [indexPath], with: .automatic)
// self.tableView.insertRows(at: [indexPathOfFirstRow], with: UITableViewRowAnimation.automatic)
tableView.endUpdates()
tableView.setEditing(true, animated: true)
} else {
//when the 'Done' is pressed this will take away the add button
tableView.beginUpdates()
let indexPathOfFirstRow = IndexPath(item: 0, section: 0)
self.tableView.deleteRows(at: [indexPathOfFirstRow], with: UITableViewRowAnimation.automatic)
tableView.endUpdates()
//otherwise the table will not be in editing mode.
tableView.setEditing(false, animated: false)
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//STEP 1 for adding a row that will fuction as add place
//If we are in editing mode a 1 will be return and the extra cell will be added
let adjustment = isEditing ? 1 : 0
//add the extra cell if we are in editing mode
return myPins.count + adjustment
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "PinCell", for: indexPath)
if indexPath.row == 0 && isEditing {
//this means we are in editing mode an we to add the cell that will be addPlace
//let indexPathOfFirstRow = IndexPath(item: 0, section: 0)
cell.textLabel?.text = "Add Place"
print("add place was called index.row is : \(indexPath.row)")
//addCell.addButton.tag = indexPath.row
//addCell.addButton.addTarget(self, action: "addPlace", for: .touchUpInside)
} else {
// let cell = tableView.dequeueReusableCell(withIdentifier: "PinCell", for: indexPath)
//this is the info I want to dispaly in the cell
let pin = myPins[indexPath.row]
cell.textLabel?.text = pin.pinName
return cell
}
//add the green plus button icon for the extra row added
func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle {
// let indexRow = IndexPath(item: 0, section: 0)
if indexPath.row == 0 {
return .insert
}
return .delete
}
//delete with a with a swipe
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
print("pins count before deleate\(myPins.count)")
if editingStyle == .delete {
let pin = myPins[indexPath.row]
guard let currentUser = FIRAuth.auth()?.currentUser?.uid else {return}
rootRef.child(child.users.rawValue).child(currentUser).child(users.myPins.rawValue).child(plotToEdit!.plotID).child(pin.placeID).removeValue()
}
//When you press on the green plus button as appose to the row.
else if editingStyle == .insert {
let autocompleteController = GMSAutocompleteViewController()
autocompleteController.delegate = self
present(autocompleteController, animated: true, completion: nil)
}
}
//When in ending more I can slect the first row that is add places
func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
// let indexPathOfFirstRow = IndexPath(item: 0, section: 0)
if isEditing && (indexPath.row > 0) {
return nil
}
return indexPath
}
I'm writing my app in Swift 3 (converted) in Xcode 8.
NSFetchedResultsController is causing a Serious Application Error for me.
My main table view is sectioned by a text identifier called "yearText" which is being set on any given Event record (NSManagedObject) when the user changes the "Event Date" with a date picker. When the picker is changed or dismissed, the year is stripped from the date, converted to text, and stored in the Event object. The managed object context is then saved.
If a date is picked for which there is already a section in existence (i.e. the year "2020") an error is thrown that says:
[error] 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 (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). with userInfo (null)
As long as the date chosen is not within a year that already has a section named after it, it all works fine.
Here is my relevant code for updating the database and tableview:
var fetchedResultsController: NSFetchedResultsController<NSFetchRequestResult> {
if _fetchedResultsController != nil {
return _fetchedResultsController!
}
// Fetch the default object (Event)
let fetchRequest = NSFetchRequest<NSFetchRequestResult>()
let entity = NSEntityDescription.entity(forEntityName: "Event", in: managedObjectContext!)
fetchRequest.entity = entity
// Set the batch size to a suitable number.
fetchRequest.fetchBatchSize = 60
// Edit the sort key as appropriate.
let sortDescriptor = NSSortDescriptor(key: "date", ascending: false)
fetchRequest.sortDescriptors = [sortDescriptor]
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: managedObjectContext!, sectionNameKeyPath: "yearText", cacheName: nil)
aFetchedResultsController.delegate = self
_fetchedResultsController = aFetchedResultsController
do {
try _fetchedResultsController!.performFetch()
} catch {
// Implement error handling code here.
abort()
}
return _fetchedResultsController!
}
var _fetchedResultsController: NSFetchedResultsController<NSFetchRequestResult>?
// MARK: - UITableViewDelegate
extension EventListViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as! EventCell
cell.isSelected = true
configureCell(withCell: cell, atIndexPath: indexPath)
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as! EventCell
cell.isSelected = false
configureCell(withCell: cell, atIndexPath: indexPath)
}
}
// MARK: - UITableViewDataSource
extension EventListViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return fetchedResultsController.sections?.count ?? 0
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let sectionInfo = fetchedResultsController.sections![section]
return sectionInfo.numberOfObjects
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "EventCell", for: indexPath) as! EventCell
configureCell(withCell: cell, atIndexPath: indexPath)
return cell
}
func configureCell(withCell cell: EventCell, atIndexPath indexPath: IndexPath) {
// bunch of stuff to make the cell pretty and display the data
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
// Return false if you do not want the specified item to be editable.
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let context = fetchedResultsController.managedObjectContext
context.delete(fetchedResultsController.object(at: indexPath) as! NSManagedObject)
do {
try context.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
//print("Unresolved error \(error), \(error.userInfo)")
abort()
}
}
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let sectionInfo = fetchedResultsController.sections![section]
return sectionInfo.name
}
func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
// make the section header look good
view.tintColor = kWPPTintColor
let header = view as! UITableViewHeaderFooterView
header.textLabel?.textColor = kWPPDarkColor
header.textLabel?.font = UIFont.preferredFont(forTextStyle: UIFontTextStyle.subheadline)
}
}
// MARK: - NSFetchedResultsControllerDelegate
extension EventListViewController: NSFetchedResultsControllerDelegate {
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: .fade)
case .delete:
tableView.deleteSections(IndexSet(integer: sectionIndex), with: .fade)
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: .fade)
case .delete:
tableView.deleteRows(at: [indexPath!], with: .fade)
case .update:
configureCell(withCell: tableView.cellForRow(at: indexPath!)! as! EventCell, atIndexPath: indexPath!)
case .move:
tableView.moveRow(at: indexPath!, to: newIndexPath!)
}
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
}
}
I hope you can offer me some suggestions. Thank you.
EDIT: Took out some code that was just getting in the way and revised .move to use .moveRow
EDIT 2: Added FRC generation code.
I met the same error when I update some properties on my Core Data managed objects.
Here is my controller func:
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch type {
case .insert:
self.tableView.insertRows(at: [newIndexPath!], with: .fade)
case .delete:
self.tableView.deleteRows(at: [indexPath!], with: .fade)
case .update:
self.tableView.reloadRows(at: [indexPath!], with: .fade)
case .move:
self.tableView.insertRows(at: [newIndexPath!], with: .fade)
self.tableView.deleteRows(at: [indexPath!], with: .fade)
}
}
Before I used newIndexPath for the update case, but I found this will cause some section rows mismatch issue when fetch result controller do some update action. Instead, using indexPath for update case is fine.