Attempt to insert rows at specific section Swift - ios

I have tableview and after I get new data I want to update a specific section. So I update the corresponding models. My initial value of rows in that section was 3, now it suppose to be 5. I did following:
self?.addOperations(items, completion: {(pathes) in
DispatchQueue.main.async {
print("output pathes - \(pathes)")
for (index, item) in (self?.arrValues.enumerated())!{
print("operations count - \(item.operations.count), section - \(index)")
}
self?.tableView.beginUpdates()
self?.tableView.insertRows(at: pathes, with: UITableViewRowAnimation.bottom)
self?.tableView.endUpdates()
}
})
Add operation is function that loads new data, adds it to arrValues (array that contain models of cell data), and then in completion block pass array of IndexPathObject.
That code crashes my app. In the log I can see:
output pathes - [[1, 1]]
operations count - 7, section - 0
operations count - 5, section - 1
In my second section I have 5 values for now. And output IndexPath says that I have 2 rows in section (1). But the error says:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 1. The number of rows contained in an existing section after the update (5) 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 (1 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
It all should work. Why doesn't it and how to fix it?
My delegate methods declared like this:
func numberOfSections(in tableView: UITableView) -> Int {
return arrValues.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arrValues[section].operations.count
}

Related

I tried to update after adding data to the list first, but an error occurs

DispatchQueue.main.async {
let data = try?JSONSerialization.data(withJSONObject: jsonDict["records"] as Any, options: [])
if let noticeData = try? decoder.decode([NoticeModel].self, from: data!){
for noticeAdd in noticeData {
self.noticeLists.append(Notice(title: noticeAdd.title, date: noticeAdd.createdAt, description: noticeAdd.content, isOpened: false))
self.noticeTableView.beginUpdates()
self.noticeTableView.insertRows(at: [IndexPath.init(row: self.noticeLists.count-1, section: 0)], with: .automatic)
self.noticeTableView.endUpdates()
}
}
self.chkList()
}
I tried to update after adding data but not working
'Invalid update: invalid number of sections. The number of sections contained in the table view after the update (1) must be equal to the number of sections contained in the table view before the update (0), plus or minus the number of sections inserted or deleted (0 inserted, 0 deleted).'
I keep getting this error only. I don't know what is causing this error.
Can you help me?

.IsEmpty returns False

I am using the following code to check if the array is empty and then return 0 number of rows for tableview to avoid the crash. But even when the array is empty it is not returning 0 row count and enters the else part which it is not supposed to do. Hence my application crashes with an index out of range exception.
I have tried putting two checks using count and isEmpty but it still enters the else part even when the array is empty.
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if HomeVC.categoryDetail.count == 0{
return 0
}else{
if(HomeVC.categoryDetail.isEmpty)
{
return 0
}
else
{
return HomeVC.categoryDetail.count + 3 as Int
}
}
}
I want to ensure that it won't enter the else part when the array has no value.
You wont. Actually you never reach HomeVC.categoryDetail.isEmpty because you checked HomeVC.categoryDetail.count == 0 earlier.
Note 1: Generally .isEmpty is much more performant than getting .count. Because there is no need to count items when you can check only the last index.
Count Complexity: O(1) if the collection conforms to RandomAccessCollection; otherwise, O(n), where n is the length of the collection. - Source
Note 2: There is no need to cast count to Int. Because it can be done automatically from UInt to Int. And count is returning as Int Already.
Note 3: Use ? : for one line two conditions situations. It will prevent you from extra codes and undefined states.
So the code would be:
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return HomeVC.categoryDetail.isEmpty ? 0 : HomeVC.categoryDetail.count + 3
}
Note 4: Try print(HomeVC.categoryDetail) before return to see what is inside when you expect to be empty and it's not.

Invalid update: invalid number of sections

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of sections. The number of sections contained in the table view after the update (3) must be equal to the number of sections contained in the table view before the update (3), plus or minus the number of sections inserted or deleted (1 inserted, 0 deleted).'
but i insert 1 and deleted one based on data source what i missed
self.states?.append(sortedStates) //Update state property
if (self.states?.count)! > 3 {
self.states?.removeFirst()
}
self.newsFeedTableView.beginUpdates()
self.newsFeedTableView.insertSections([(self.states?.count)! - 1], with: .none)
if (self.states?.count)! > 3 {
let statesForoldestStateTime = self.states?.first
self.newestStateTime = statesForoldestStateTime?.first?.createdAt
let indexpostion = (self.states?.count)! - 3
self.newsFeedTableView.deleteSections([indexpostion], with: UITableViewRowAnimation.none)
}
self.newsFeedTableView.endUpdates()
The error says it all. When if (self.states?.count)! > 3 is false. The only section would be inserted and not deleted.
You should update your data source accordingly. The number of sections method must return someArray.count. When you insert some section, make sure to update that some array, and when you delete some section, delete the element from some array. That will resolve the issue.

Populate Number of rows in section from CoreData when there are 2 sections

In my CoreData, I have a Person entity, and each Person can have multiple (to Many) Statement entities. The statement entity has an attribute called amountOwed which is a decimal amount. Right now my idea is to loop over all the amounts and add them up, if they of a positive amount add them to the positive array and if they are negative amount add them to that array. Then use that array to figure out how many cells each sections needs to display.
I created a fetchedResultsController and I am trying to use that for the for loop
for i in 0..<fetchedResultsController.fetchedObjects!.count {
let person = fetchedResultsController.fetchedObjects?[i]
let amountTotal = person?.value(forKeyPath: "statement.#sum.amountOwed") as? Decimal
if(amountTotal! <= Decimal(0) )
{
postiveCellNumber += 1
print("\(postiveCellNumber) postive number count")
}
else{
negativeCellNumber += 1
print("\(negativeCellNumber) negative number count")
}
}
Then, I'm trying to use those arrays in the numberOfRowsInSection function like so:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch(section) {
case 0:
return postiveCellNumber
case 1:
return negativeCellNumber
default :return 0
}
}
I don't think my for loop is looping properly as I get an error that says
no object at index 2 in section at index 0.
How about using two different queries, one for the positive values and one for the negative ones, with two different fetched results controllers? Then you can let the FRC do the iterating and counting for you.
You won't be able to use the FRCs to manage sections. You'll have to do that yourself. Specify nil for the sectionNameKeyPath. Then you want something like
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return positiveResultsController.fetchedObjects?.count ?? 0
}
else {
return negativeResultsController.fetchedObjects?.count ?? 0
}
}
or maybe
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
let sectionInfo = positiveResultsController.sections![section]
return sectionInfo.numberOfObjects
}
else {
let sectionInfo = negativeResultsController.sections![section]
return sectionInfo.numberOfObjects
}
}
with similar logic for tableView(_:cellForRowAt:).
It's very difficult to identify the specific cause of the error, without seeing more of how your FRC and tableView are constructed. But this bit looks suspicious:
if(amountTotal! <= Decimal(0) )
{
postiveCellNumber += 1
print("\(postiveCellNumber) postive number count")
}
Surely the if condition is back to front: you should increment the positiveCellNumber if amountTotal! >= Decimal(0)?
But that aside, even if you successfully calculate the counts, you will then face the issue of working out which of the FRC's fetchedObjects should appear in each row of each section:
You could do this "on the fly" in cellForRowAt, but that will involve iterating through the fetchedObjects again, to determine which appear in which section, which is clumsy and inefficient.
You could separate the fetchedObjects out into two separate arrays as a one-off step, once the FRC has done its performFetch. But you then need pretty ugly FRC delegate methods to update the two arrays whenever the FRC's fetchedObjects array is updated.
You could configure your FRC to automatically assign its fetched objects to the correct section, by specifying a sectionNameKeyPath and associated sort descriptors for the underlying fetch. The problem here is that it is not possible to sort the fetch using a calculated figure. If you want to pursue this route, you will need to add a totalAmountOwed attribute to your Person entity, and to ensure it is updated whenever the related Statements change.
Alternatively, you could follow #HalMueller's suggestion, and use two separate FRCs. You would use complementary predicates for the underlying fetches, one to get only those Persons with positive amount owed, the other to get those with negative amount owed. You can then use the fetchedObjects array for one FRC to populate section 0, and the other FRC to populate section 1. Overall, I think this is the solution I would recommend.

How to implement NSFetchedResultsController delegate when multiple row is on deletion?

Two items is deleted.
func controller(controller: NSFetchedResultsController!, didChangeObject anObject: AnyObject!, atIndexPath indexPath: NSIndexPath!, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath!) {
switch(type) {
case .Delete:
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
default:
break
}
}
At deletion line I get this error:
CoreData: error: Serious application error. Exception was caught
during Core Data change processing. This is usually a bug within an
observer of NSManagedObjectContextObjectsDidChangeNotification.
Invalid update: invalid number of rows in section 0. The number of
rows contained in an existing section after the update (0) must be
equal to the number of rows contained in that section before the
update (2), plus or minus the number of rows inserted or deleted from
that section (0 inserted, 1 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)
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid
number of rows in section 0. The number of rows contained in an
existing section after the update (0) must be equal to the number of
rows contained in that section before the update (2), plus or minus
the number of rows inserted or deleted from that section (0 inserted,
1 deleted) and plus or minus the number of rows moved into or out of
that section (0 moved in, 0 moved out).'
You need to 'batch' the updates by also implementing the controllerWillChangeContent and controllerDidChangeContent delegate methods as follows (although sorry these are Obj-c):
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
[self.tableView beginUpdates];
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[self.tableView endUpdates];
}

Resources