Which tableView cells are visible after a batch update? - ios

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?

Related

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

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.

UITableView Delete, Insert & Move ordering in batch updates

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.

Infinite UITableView Scroll with NSFetchedResultsController

I'm trying to use this method to implement an infinitely scrolling UITableView
The core logic of the solution is:
To increase the tableview content by a factor of 3, so that we make the 3 copies of the content laid one after another vertically.
Whenever the top end of the scroll is reached, move the the scroll offset back to start of the 2nd copy
When the bottom end of the scroll is reached, we move the scroll offset back to the start of the 2nd copy minus the height of the tableview, so that we end up showing the same content as we are now.
This means that a single cell insert or delete actually results in three inserts or deletes. Because my table's datasource is populated by an NSFetchedResultsController it causes an Assertion Failure.
The number of rows contained in an existing section after the update (12) must be equal to the number of rows contained in that section before the update (15), 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).
Is there some way to stop the program from crashing in these cases? I'd really appreciate any help/pointers. Thanks.
This can be kind of tricky in iOS especially when you're first starting out. The key is to update the datasource that is backing your UITableView before you inform the tableview of changes.
For instance, if you have an NSArray backing your tableview, then you will want to remove or add items to it, prior to calling reloadSection: on the tableview.
Although it is the least optimized solution, while your testing feel free to simply call reloadData which will ignore what the tableview has cached and force it to recalculate based on whatever is backing the tableview.
You can use NSFetchedResultsControllerDelegate method controller:didChangeObject:atIndexPath:forChangeType:newIndexPath:. To insert\delete 3 objects.
To keep it simple you could return 3 for number of sections in your table view (this will handle getting 3 copies of objects). And then in your NSFetchedResultsControllerDelegate update objects in all sections of your UITableView (insert, delete or move). You should use row number from the NSFetchedResultsControllerDelegate and apply all changes 3 times each for different section. Remember to keep it consistent with UITableView data source method tableView:numberOfRowsInSection:.

fetchedResultsController quantity and frequency of object fetching

I am using a FetchedResultsController to fetch the data for my UITableView. The data is created via actions performed on another tab (so my table may have 5 items but if I switched tabs and go back to my table, it may have more than 5 items that it should display). My table can potentially contain many rows. Right now I am using [myFetchedResultsController performFetch] in my viewDidLoad.It appears that when I create data in my other tab, when I switch back to my table tab, that new data is put into my table automatically without me perfomring [myFetchedResultsController performFetch] again. Here are my questions:
1) Does a fetchedResultsController automatically monitor the manajedObjectContext for changes and fetches the new objects if they come into existence? (This appears to be what is happening but I just want to make sure. Perhaps I have some code that is helping me do this that I forgot I put in somewhere)
2) Does the fetch performed by [myFetchedResultsController performFetch] fetch all of the objects at that time, or does it fetch only what it can fit in the view of the table and it fetches the rest later as it needs it (as you scroll in the table)? I ask because since my table can potentially have a lot of rows, it seems inefficient to fetch all the data at once if only ~12 of them will be displayed on the table at once.
EDIT: I just realized that in my FetchedResultsController delegate methods, I have
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
[self.tableView reloadData];
}
Am I correct in saying that a fetchedResultsController monitors for change, but will not apply it to the table unless the table is reloaded as I have done? If so, then I have another question about UITableView. Does reloading the table only reload the rows in view and then the other rows are updated as you scroll? Again I ask because if my data is very large, it seems inefficient to reload the entire table if it will reload all rows at once.
Yes, if you add a delegate
You should set the fetch request batch size when you configure the FRC because it can only load an appropriate number of items for the screen if you tell it how many that is.
You apply the changes, the FRC just collects and supplies the data. The delegate method tells you about a change. Reloading affects the whole table in terms of row count but only shows the visible rows (assuming the batch size is set appropriately).

Resources