Error deleting a section on UICollectionView - ios

I have 3 sections that are populated by an array of arrays. Each section has a header and a button that deletes the current section. When I tap the delete button I get the following Error:
Error:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of items in section 0. The number of items contained in an existing section after the update (8) must be equal to the number of items contained in that section before the update (3), plus or minus the number of items inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of items moved into or out of that section (0 moved in, 0 moved out).'
This is the code for the delete function
#IBAction func deleteSection(_ sender: UIButton) {
// grab the tag representign the section
let section = sender.tag
// Update data model
all.remove(at: section)
// reload data to reflect section change
// ignored due to delete sections
photosCollection?.performBatchUpdates({
self.photosCollection?.deleteSections(NSIndexSet(index: section) as IndexSet)
}, completion: { (finished) in
if finished {
self.photosCollection?.reloadData()
}
})
}
all is my array of arrays.

Related

SwiftUI list: NSInternalInconsistencyException / invalid number of rows

I have a view CountriesListScreen, to which I am passing a list of items.
struct CountriesListScreen: View {
var listItems: CountriesListItems
var body: some View {
VStack {
Section(header: CountriesListHeader()) {
ForEach (listItems, id: \.name) { item in
...
}
}
}
}
}
This is what I am doing:
I am first displaying CountriesListScreen, with 9 list items
I then go to another view, where I perform an action, which modifies the list of items
I then go back to CountriesListScreen, passing the new listItems object, which has now 5 items instead of 9
I am getting this NSInternalInconsistencyException, which I don't know how to solve:
*** Assertion failure in -[_TtC7SwiftUIP33_BFB370BA5F1BADDC9D83021565761A4925UpdateCoalescingTableView
_Bug_Detected_In_Client_Of_UITableView_Invalid_Number_Of_Rows_In_Section:],
UITableView.m:2471 2021-06-11 18:22:55.908398+0300
iosApp[6525:5802640] *** 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 (9) must be equal to the number of
rows contained in that section before the update (9), plus or minus
the number of rows inserted or deleted from that section (0 inserted,
4 deleted) and plus or minus the number of rows moved into or out of
that section (0 moved in, 0 moved out).'
If I understand correctly, SwiftUI triggers the error, as it realizes that the list of items has changed, and the change hasn't happened within the view.
How can I safely, redraw the list with a different number of items, without having this error triggered?
UPDATE: (giving more context)
The views involved are 3:
the parent of CountriesListScreen, let's call it MainView, which references an ObservedObject, which has listItems as a property
CountriesListScreen, which receives listItems as a parameter from its parent
a third view, which performs an action that modifies the listItems (and hence the ObservedObject), triggering an update of the MainView

Invalid number of rows in section while trying to reload using reloadsections

I have two sections in the tableview.
Rows in section 0 would change when receiving remote notification (insert) or an expired timer (delete).
Section 1 would always have 8 rows and would change when user refresh, all the 8 items would be changed.
I am trying to reload the entire section 0 after the dateset has changed (use refreshed) using.
self.candidates.remoteAll()
for object in json {
let candidate = Candidate(/* Some data */)
self.candidates.append(candidate!)
}
self.tableView.reloadSections(IndexSet(integer: 1), with: .automatic)
and it somehow throws
Fatal Exception: NSInternalInconsistencyExceptionInvalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (0), 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).
The number of inconsistent rows are not fixed, sometimes there are 2 before the update and 1 after, sometimes is 0 before and 1 after the update. My guess is that section 0 was inserting or delete rows while user trying to refresh section 1.
But I was trying to refresh section 1, section 0 should do no effect whether or not it has a consistent number of rows. Where am I doing wrong? Thank you.
Update
Here is numberOfRowsInSection
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section {
case 0:
if let invitations = self.invitations {
return invitations.count
}
return 0
case 1:
return self.candidates.count
default: return 0
}
}
I am using Realm Result on section 0, and Array on section 1.
I think that in this line of code should be: self.candidates.removeAll()

Dispatch.main.async invalid number of rows in section

I'm having an issue when I check off my task to quickly, which makes the app crash and gives me error:
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 (7) must be equal to the number of rows contained in that section before the update (9), 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).'
Code when radiobutton tapped
func RadioTapped(_ cell: TableViewCell) {
if let indexPath = tableView.indexPath(for: cell) {
// Removes task from coreData
let task = self.tasks[indexPath.row]
self.context.delete(task)
do {
self.tasks = try self.context.fetch(TodayTask.fetchRequest())
(UIApplication.shared.delegate as! AppDelegate).saveContext()
// Animate the removal of task cell
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(800),execute: {
self.tableView.deleteRows(at: [indexPath], with: .fade)
})
} catch {
print("Fetching failed")
}
}
}
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 (7) must be equal to the number of
rows contained in that section before the update (9), 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).'
This crash means when you are deleting your table row. You are not updating your arrayCount for instance you have defined 8 inside your numberOfRowsInSection method then before you delete the row you will also need to update the rows count based on the rows you want to delete. Otherwise when your deleteRows will called it will also called the numberOfRowsInSection method and there count will mismatch and it will crash.

FirebaseTableViewDataSource crashes on user signout and signin

My app has a UITableViewController which uses a FirebaseTableViewDataSource (from FirebaseUI). The table shows the user's bookmarked posts correctly, but when I log that user off, and log another user in, the app crashes with the following message:
* 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 (1) 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 (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).'*
I suspect the problem is related to how FirebaseUI updates the content of the tableView. I've being debugging this for the past 4 days; searched the SO questions; read the relevant docs but none mentions this unique issue. Any help will be much appreciated.
The Details (Sorry it's actually long)
This is my database structure:
|
+-posts
| |
| +-$postid
| |
| +-/* properties of a post: title, text, etc */
|
+-users
|
+-$userid
|
+-bookmarks
|
+-post1: true
|
+-post2: true
I am using FirebaseUI to show a user his/her bookmarks by passing the users/$userid/bookmarks ref to FirebaseTableViewDataSource as a query. Then for each key, I observe a single value event on posts/$postid in order to retrieve the post details...code below:
self.authStateListenerHandle = FIRAuth.auth()?.addStateDidChangeListener { auth, user in
guard let user = user else {
return
}
self.query = FIRDatabase.database().reference().child("users").child(user.uid).child("bookmarks")
self.tableViewDataSource = FirebaseTableViewDataSource(query: self.query!, view: self.tableView, populateCell: { (tableView, indexPath, snapshot) -> UITableViewCell in
if snapshot.exists() {
let postRef = FIRDatabase.database().reference().child("posts").child(snapshot.key)
let cell = tableView.dequeueReusableCell(withIdentifier: BookmarksVC.reuseIdentifier, for: indexPath) as! MiniTableCell
postRef.observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.exists() {
let post = Event(snapshot: snapshot)!
let postVM = EventViewModel(post: post)
cell.populateCellWithEvent(postVM)
cell.delegate = self
}
})
return cell
}
else {
return UITableViewCell()
}
})
self.tableView.dataSource = self.tableViewDataSource
}
I put the above code in viewDidAppear and then remove the authStateListenerHandle in viewWillDisappear like so
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if let handle = self.authStateListenerHandle {
FIRAuth.auth()?.removeStateDidChangeListener(handle)
}
}
Almost everything works fine except, when I am viewing the bookmarks for a user, then log out and log back in, the app crashes with the message
* 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 (1) 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 (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).'
*
In the viewWillDisappear set the self.tableViewDataSource = nil. So, that you don't update the dataSource improperly.

CollectionView.DeleteItems throws exception NSInternalInconsistencyException

I'm getting this exception when I try to call DeleteItems on my UICollectionView. I've googled around and most people are saying they solve this by NOT calling ReloadData on the CollectionView after they call DeleteItems, but I know I'm not calling that. I even overrode it and put a break point on ReloadData and confirmed that it's not getting called.
this.InvokeOnMainThread(() =>
{
CollectionView.DeleteItems (new NSIndexPath [] { indexPath });
});
Objective-C exception thrown. Name: NSInternalInconsistencyException Reason: Invalid update: invalid number of items in section 0. The number of items contained in an existing section after the update (5) must be equal to the number of items contained in that section before the update (5), plus or minus the number of items inserted or deleted from that section (0 inserted, 1 deleted) and plus or minus the number of items moved into or out of that section (0 moved in, 0 moved out).
Looks like I needed to update my list that was tied to my datasource BEFORE calling DeleteItems:
this.InvokeOnMainThread(() =>
{
this.MyList.RemoveAt(indexPath.Row);
CollectionView.DeleteItems (new NSIndexPath [] { indexPath });
});

Resources