UITableView Delete, Insert & Move ordering in batch updates - uitableview

UITableView updates between beginUpdates and endUpdates calls are batched together and all performed at the same time. Apple's documentation is specific about the order in which insert and delete operations are performed:
Deletion and reloading operations within an animation block specify which rows and sections in the original table should be removed or reloaded; insertions specify which rows and sections should be added to the resulting table. The index paths used to identify sections and rows follow this model. Inserting or removing an item in a mutable array, on the other hand, may affect the array index used for the successive insertion or removal operation; for example, if you insert an item at a certain index, the indexes of all subsequent items in the array are incremented.
It's also worth noting that:
Calls to beginUpdates and endUpdates can be nested; all indexes are treated as if there were only the outer update block.
[Emphasis mine]
So: think about deletions as occurring in a first pass, and then any insertions happening following this, using the new index paths that result after the deletions.
That's useful. However, I've not found any documentation about where row (and section) moves occur, which indexing they should use, and whether this impacts on the other steps. Anyone know?

When moving table row, you specify two indexes:
indexPath in original table from where to take this row
newIndexPath in resulting table where this row will appear
func moveRow(at indexPath: IndexPath, to newIndexPath: IndexPath)
Possible conflicting operations in single batch, causing app crash:
trying to move a row you also deleting
trying to move same row to multiple destinations
trying to move several rows to the same destination
trying to move a row to the same destination where you insert new row
It is not possible to move a row you are inserting, because it was not there in original table.
By default, moved row will be not reloaded from data source.

Related

Which tableView cells are visible after a batch update?

I am confused about how moves of table view rows in batch updates are to be handled.
I have a tableView that is updated in a batch update block:
tableView.performBatchUpdates {
//…
}
Within the block, deletes, inserts, updates and moves have to be processed.
The Apple docs say
Deletes are processed before inserts in batch operations. This means
the indexes for the deletions are processed relative to the indexes of
the table view’s state before the batch operation, and the indexes for
the insertions are processed relative to the indexes of the state
after all the deletions in the batch operation.
I assume that the indexes of moves and updates relate to the state after deletes and inserts have be done, although this is not explicitly stated.
In my case, I have to configure a table view cell after it has been moved. But there are different cases to be considered:
If a moved row is visible before and after the move, I can access the cell using let cell = tableView.cellForRow(at: IndexPath), and configure it. But which index path is to be used: before or after the move?
If a moved row is visible only before the move, it is not necessary to configure it.
If a moved row is visible only after the move, there is not yet a cell that can be configured.
Of course, I could store which rows have been deleted, inserted and moved until the batch update is finished, and check in the completion block if an inserted or moved row is visible, and configure its cell if so.
Is this the right way to do?

Insert and delete rows in UITableView before first appearance on screen

I perform a bunch of row/section inserts and deletions with insertSections, insertRows, deleteRows etc. during the startup of a view controller. The manipulations are triggered by events of a background process (of course the actual calls to the table view are performed on the main thread; also, they are wrapped in beginUpdates and endUpdates).
When I start off these row manipulations in viewWillAppear I eventually get a crash because of an inconsistency in the row data: attempt to delete row 1 from section 0 which only contains 1 rows before the update. When this happens, there actually are 2 rows in the section before the update which apparently is not correctly recognized by the table view.
However, when I start the exactly same sequence of manipulations in viewDidAppear, there's no crash and the rows animate in and out as expected.
This looks like the table view has problems with a fast-running sequence of inserts and deletes before it appears. Is this a known limitation, or do you have another explanation for this issue?
Looks like you dont need to manipulate actual UITableViewCells, you may just manipulate some kind of view models (plain objects), prepare them, and then reload UITableView with them on viewWillAppear

Do I have to call begin/endUpdates method when a new row is inserted into the tableView?

Description:
A table view with 3 rows is created when the app is started. A add button in the navigation bar will add a new row into the table view if tapped. The core process behind the add row action is the method func insertRows(at:, with:).
Apple documents and many stack-overflow answers (e.g., this one) suggest that put any method calls that insert, delete, or select rows and sections of the table view between function beginUpdates() and endUpates().
I removed the beginUpdates() and endUpdates(); it turns out the app works just fine without them.
Question:
What are the benefits of adding beginUpdates() & endUpdates()?
Why I ask this question:
The function func insertRows(at:, with:) is calling the delegate function tableView(_:, cellForRowAt:) -> UITableViewCell to create a new cell via dequeueReusableCell(withIdentifier:, for:) -> UITableViewCell, this is the same procedural the tableView creates the 3 default rows when the app started, but, there is no such a beginUpdates / endUpdates there. So why do I need to add this procedure later when I add a new row, while without them the app still seems to be working?
To animate a batch insertion, deletion, and reloading of rows and sections, call the corresponding methods within an animation block defined by successive calls to beginUpdates and endUpdates. If you don’t call the insertion, deletion, and reloading methods within this block, row and section indexes may be invalid. Calls to beginUpdates and endUpdates can be nested; all indexes are treated as if there were only the outer update block.
At the conclusion of a block—that is, after endUpdates returns—the table view queries its data source and delegate as usual for row and section data. Thus the collection objects backing the table view should be updated to reflect the new or removed rows or sections.
Apple Docs

UITableView not refreshing when numberOfRowsInSection = 0

I have a UITableView that presents a list of nearby places. I can filter the table using a UISegmentedControl by the type of place (bar, restaurant, etc.) The issue is that when I attempt to filter the list by a segment that has 0 records, the previous segments records are not being replaced with an empty table.
Every time I click on a different segment, I'm calling tableView.reloadData() and feed the table a filtered array containing places of the selected type.
From my experience , The issue lie in the logic that you have made on dynamically change of height of tableView hopefully because only cellForRowAtIndexPath will not called in case of your number of rows are 0.It reload but some logic in cellForRowAtIndex path is there which is never called.

Batch update table row, reorder & update at the same time?

Description
I have a CoreData entry called Person, I fetch it using NSFetchedResultsController, with a fetchRequest ordered by property "name". Then I display the "name" in the table view cell.
Problem
When I change the entity's "name" property and the rows reorder, NSFetchedResultsControllerDelegate does give me a NSFetchedResultsChangeType.move. But the "name" displayed on the cell is outdated, meaning I'm not receiving NSFetchedResultsChangeType.update
The Table View Programming Guide: Inserting and Deleting Rows and Sections says batch updates do updates first, then deletions, lastly insertions.
It defers any insertions of rows or sections until after it has handled the deletions of rows or sections. The table view behaves the same way with reloading methods called inside an update block—the reload takes place with respect to the indexes of rows and sections before the animation block is executed. This behavior happens regardless of the ordering of the insertion, deletion, and reloading method calls.
Question
Any idea on how the notifications sent by NSFetchedResultsController are implemented, specifically on the ordering of insert, delete, update & move?
Or how can I use some kind of code to efficiently (meaning partial update, not reload all data) solve this particular problem?
Your setup is non-standard. The sectionNameKeyPath is really meant for sections not rows. Fetch the Person entity and populate the cell with a person's name directly via itemForRowAtIndexPath.
You will then have the expected change types available.

Resources