I have an app wherein the datasource for a UITableView updates by a background thread from a remote server every 30 seconds.
A crash occurs if the user is scrolling the tableView or if the tableView is in process of reloadTableView:
. The reason for crash is that number of rows in table at the time of the crash doesn't match the number of rows at the time the redraw started.
Another crash happens when a requested TableView cell is out of range because, between the time numberofTableViewCells:
is called and the time cellfForRowAtIndexPath
is called, the datamodel has changed and the cell is no longer there.
The data for the tableView is updated from a background thread. While data is loaded from a server, the user should still be able to interact with tableView but right now that causes a crash.
How do I block the tableView from scrolling or reloading while I update tableViewDataSource? What is the best practice for this kind of situation?
Thanks.
The short answer is that you need to buffer the data in your data model and only update the table with the new data when the table is not being scrolled. That needs to be done in the data model because the datasource delegate has no idea what is inside the data model or what has changed.
However, from a UI design perspective, having a table actively updating while the user is scrolling will disorient the user. The user will think they are in the top/middle/bottom of the table then suddenly find themselves in the bottom/top/middle. The user will think that they have viewed all the data in one section of the table but in reality the table will have added something they needed to see. For example, if the table is a list of alphabetized names, the user checks all names staring with "U", sees that there are none and then goes to check somewhere else in the table. Meanwhile, the table invisibly updates the "U" section with fresh names while the user is looking elsewhere. Even if the user understands that the table is dynamically updated (which most are not) they will have to continually scroll around checking virtually the entire table to see what has changed.
A better UI design is to give the user the ability to update. Put an "Update" or "New Data Available" button in the bar and then set it to appear when new data arrives. Upon clicking the button, freeze the table, update and only then let the user resume interaction. It would also be good design to visually flag the rows added.
This will make the UI more understandable for the user and solve your crashing problem at the same time.
If you don't use Core Data, in order to implement live and invisible updating of the table. you will need to freeze the in the calls – tableView:numberOfRowsInSection
or – numberOfSectionsInTableView:
before – tableView:cellForRowAtIndexPath:
is called.
Since – tableView:numberOfRowsInSection
is called first, I would put a call there to first update and then freeze your data model. That way the data model will return the proper number of sections and rows.
I presume you will have to divide you data model into two sections, one of which will buffer incoming data and another that will order the data for display. Your update method should move all the finalized buffered data into the display data section.
In addition, you will probably need to set a timer for when the user is not moving the table. The timer should call the update method if the table is not being actively manipulated and then it should force an update.
If you use Core Data, you can use NSFetchedResultController
and it's delegate methods will inform you when the data model changes. It should return the proper section and row information updated live. It's pretty easy to drive an updating table this way. However, it will not overcome the problem of data entering the model so quickly that the model changes between method calls. You will still have to freeze and/or slow down the model. You will however, not need the timer.
Core Data is your best option but even so it's going to be difficult to implement because you're trying to do something against the grain of the UI and therefore the API does not easily support it.
Looking back over this answer, I see that I neglected to mention [UITableView beginUpdates]
which will freeze the table's configuration while rows are added or deleted. It is paired with [UITableView endUpdates]
to include the changes in the UI.
It's not safe to call methods on GUI objects (like a UITableView) from a background thread.
You need to do something like:
[ tableView performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:NO]
See uitableview-drawing-problems-when-reloaddata-is-called
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With