SwiftUI list: NSInternalInconsistencyException / invalid number of rows - ios

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

Related

Error deleting a section on UICollectionView

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.

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.

NSInternalInconsistencyException occurs when insertng indexPath into tableView but only when row count gets above a certain threshold

I have two tables in which I am dragging and dropping between them: sourceTableView and targetTableView. They each have their own tableViewController. A parentContainerViewController manages a 'slide out' UI which allows the user to 'longPressGesture' on the sourceTableView cell which causes sourceTableView to slide out exposing the targetTableView below at which time the user can 'drop' the dragged cell into place within the targetTableView. All works well as it should until the targetTableView grows to about 21-27 rows in size. Once we get to this size and if I re-run the project I start to get the NSInternalInconsistencyException. The detail of the error reads: reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (27) 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 (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).'
At first I thought I was getting a race condition issue since the number of rows before the update is "0" and after it is "27". Maybe the tableView wasn't able to load before the user drags in the item and calls the insertIndex method on tableView. But then I noticed that it works fine until we get to a certain length. Race condition wouldn't make sense in this case. Now I am thinking it has something to do with with 'visible cell' loaded vs not loaded. But why would it be '0' rows? I have looked all over for the answer. There are many NSInconsistency but none that seem to answer my specific problem.
This is the function call to tell targetTableViewController to insert an item into its datasource then tells targetTableView to insertRowsAtIndexPath :
dragDropDataSource.tableView(self, insertDataItem: item, atIndexPath: indexPath)
self.beginUpdates()
self.insertRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Fade)
self.endUpdates()
Here is the targetTableViewController code implementing the above call:
func tableView(tableView: UITableView, insertDataItem dataItem : AnyObject, atIndexPath indexPath: NSIndexPath) -> Void {
if let di = dataItem as? DataItem {
data.insert(di, atIndex: indexPath.row)
}
data is the array that stores the 'DataItems' which are shown in the tableView rows.
Additionally. The targetTableView is initialized with data via a call in its viewDidLoad() to getMyTopTen()
func getMyTopTen() {
let pwAPI = PowWowAPI()
pwAPI.getMyTopTen(self.boardId!) {
(entries:[Entry])in
for(var i=0; i < entries.count; i++) {
let dataItem = DataItem(indexes: String(i), entry: entries[i])
self.data.append(dataItem)
}
}
dispatch_async(dispatch_get_main_queue()) {
self.targetTableView.reloadData()
}
}
I hope I have explained the problem sufficiently. If not please help me make this question better. I am new to asking questions here.

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

NSInternalInconsistencyException when removing rows from a UITableView

I'm receiving an error indicating that I need to update the number of rows in my UITableView after I have deleted a row. I realise that It's because I've not updated the amount of rows in the UITableView, I'm just not sure how to make this change in the code I have below.
Any assistance is really appreciated.
!-- code
NSIndexPath[] rowsToReload = new NSIndexPath[] {
NSIndexPath.FromRowSection(1, 1)
};
dv.TableView.BeginUpdates ();
dv.TableView.DeleteRows(rowsToReload,UITableViewRowAnimation.Automatic);
dv.TableView.EndUpdates ();
!-- error
Name: NSInternalInconsistencyException Reason: Invalid update: invalid number of rows in section 1. The number of rows contained in an existing section after the update (3) must be equal to the number of rows contained in that section before the update (3)
Your code above is OK. However, you need to call it after you have removed the corresponding items from your data source.
For example, if you are filling your table view from a List<string> (myList) and you wanted to remove the first row:
myList.RemoveAt(0); // remove the item from the list first
NSIndexPath[] rowsToDelete = new NSIndexPath[] {
NSIndexPath.FromRowSection(0, 0)
};
dv.TableView.DeleteRows(rowsToDelete, UITableViewRowAnimation.Automatic);
No need to wrap the DeleteRows call in a Begin/EndUpdates, as you only have one delete action to perform. That is used when you have multiple different actions (eg. DeleteRows + InsertRows) so that the animations are performed smoothly.
#Dimitris thanks for answering my question. I ended up solving the problem by:
calling the reload method on my UITableView
NSIndexPath[] rowsToReload = new NSIndexPath[] {
NSIndexPath.FromRowSection(1, 1)
};
dv.TableView.ReloadRows (rowsToReload,UITableViewRowAnimation.None);
in my UITableViewCell GetCell method
I removed the label, field and button that were included on the first load of the table. Since this screen in my app will be seen only once, when the user loads it for the first time.

Resources