In what order are UITableView hook methods called? - iphone

In what order are the following methods called for a UITableView?
numberOfSectionsInTableView
numberOfRowsInSection
cellForRowAtIndexPath
heightForRowAtIndexPath
didChangeObject (NSFetchedResultsController)
Please include any other pertinent hook methods that I may have left out.

I would be wary about being concerned with the order in which these methods are called. These methods have very specific, and complete purposes. Even though the methods are called in a certain order right now, that doesn't mean they will always be called in that order so it is dangerous to make assumptions in your code as to what order they are called. For example, cellForRowAtIndexPath is not called for all rows. It is only called for visible rows and then called for additional rows as the user scrolls to them. Old rows are also destroyed as they go off the screen and it will request the cells again if the user scrolls back up.
Bottom line, since the order is not specified in Apple's documentation, you cannot safely assume that they will always be called in the same order and it is generally not a good idea to make such assumptions in your implementation.

I eventually did use NSLog statements to figure this out but unfortunately didn't document it. For, I realized that the order those functions are called didn't matter.
Reading about Batch Insertion, Deletion, and Reloading of Rows and Sections in the Table View Programming Guide for iOS I learned to update the data before inserting rows in the table view.

Related

Perform time consuming tasks inside UITableViewCell, pausing on scrolling

I have TableView with customs cell representing events. It looks very close to first and third image here.
As you can see (sorry for small resolution) on some events there are photos of friends that are going to participate.
Unfortunately information about friends is not loaded with other information about events.
So after I got list of events I can make request to load list of friends that are going to participate in each event.
Right now I use code like
class EventCell : UITableViewCell {
var eventForCell : Event? {
didSet {
eventTitleLabel.text = eventForCell.title
eventDateLabel.text = eventForCell.date
presentFriends(eventID : eventForCell.id)
}
}
func presentFriends(eventID : Int) {
//searching for friends for event with specific eventID
.......
.......
//for every friend found adding UIImageView in cell
for friend in friendsOnEvent {
let avatar = UIImageView(....)
self.addSubview(avatar)
}
}
}
This code works but photos are not presented in smooth way. Also if you scroll list fast they start to blink. Maybe it is even not necessary to load them if user scrolls fast list of events. So I have two questions:
How can I make smooth scrolling experience taking in consideration that presenting friends for every event can take time and sometimes it finishes after cell was scrolled away.
If I had loaded list of events and already presenting cells with them. How can I update those cells after I get information about friends that are going to participate?
When user is scrolling and I am creating async tasks to display some images in cell I think I should use weak reference to self and maybe check it not to equal nil so task would be canceled if cell is not visible now. How should it be done?
Update:
I found info about tableView(_:prefetchRowsAt:) method inside UITableViewPrefetchingDataSource protocol, should I use it for this case? Maybe someone has some experience with it?
1. (Re)creating a view objects during cellForRowAt is generally a bad practice. From the screenshot I assume that there is a limit to how many avatars are there on a single cell - I would recommend creating all the UIImageView objects in the cell initializer, and then in presentFriends just set images to them, and either hide the unused ones (isHidden = true) or set their alpha to 0 (of course, don't forget to unhide those that are used).
2. If you are using SDWebImage to load images, implement prepareForReuse and cancel current downloads to get a bit of performance boost during scrolling, and prevent undefined behaviour (when you try to set another image while the previous one was not yet downloaded). Based on this question, this one and this one I would expect something like:
override func prepareForReuse() {
super.prepareForReuse()
self.imageView.sd_cancelCurrentImageLoad()
}
Or you can use [this gist][4] for an inspiration.
P.S.: Also, you will have to count with some blinking, since the images are downloaded from web - there will always be some delay. By caching you can get instantly those that were already downloaded, but new ones will have delay - there is no way to prevent that (unless you preload all the images that can appear in tableView before presenting tableView).
P.S.2: You can try to implement prefetching using [UITableViewDataSourcePrefetching][6]. This could help you out with blinking caused by downloading the avatars from web. This would make things a bit more complicated, but if you really want to remove that blinking you will have to get your hands dirty.
First of all, as you can see from the documentation, prefetchRowsAt does not give you a cell object - thus you will have to prefetch images to your model object instead of simply using sd_setImage on the UIImageView object at a given cell. Maybe the aforementioned gist would help you out with downloading images to model.
Now also as the documentation states:
Loading Data Asynchronously
The tableView(_:prefetchRowsAt:) method is not necessarily called for every cell in the table view. Your implementation of tableView(_:cellForRowAt:) must therefore be able to cope with the following potential situations:
Data has been loaded via the prefetch request, and is ready to be displayed.
Data is currently being prefetched, but is not yet available.
Data has not yet been requested.
Simply said, when you are in cellForRowAt, you cannot rely on prefetchRowsAt being called - it might have been called, and it might not have been called (or maybe the call is in progress).
Documentation continues to suggest using Operation as a backing tool for downloading the resources - prefetchRowsAt could create Operation objects that would be responsible for downloading avatars for a given row. cellForRowAt can then ask the model for the Operation objects for the given row - if there are not any, it means that prefetchRowsAt was not called for that row, and you have to create Operation objects at that point. Otherwise you can use the operations created by prefetchRowsAt.
Anyway, if you decide that it is worth it, you can use this tutorial as an inspiration for implementing prefetching.
You can use UITableViewDataSourcePrefetching as you mentioned.
It's a protocol that calls your prefetch data source when some cells are going to be displayed but are not on the screen yet.
This way you can prepare all the resources that takes time to load before they are presented.
You just have to implement:
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath])
and fetch the data related to the cells from all of the indexpaths.
Beware it's only available since iOS10.
I personally use AlamofireImage with a cache so images that have already been downloaded aren't fetched twice, there's plenty of alternatives but it's a good practice to use cached images on this kind of scenario.

How to update UITableView at run time, after the view is loaded

I have read several articles about UITableView, including the official doc and some on SO. But my situation seems to be different.
I want to update the Table each time the view loaded. And I must fetch the data using HTTP request.
What I got now is:
When enter the table view, I should use a non-synchronous HTTP request to update the data. Because I don't want the main thread to wait. One place to do that, is in the tableView:cellForRowAtIndexPath: method. So I return 0 for no data exist at the beginning.
When I get the HTTP respond, I update rows on main thread using beginUpdates endUpdates insertRowsAtIndexPaths:withRowAnimation:
And I must update the "Data Source" at the same time, but how to do that?
Or should I make a daemon thread and update my data every once in a while? So that the data will be ready when TableView is loaded.
You would do it like this:
Have a boolean or some variable where you can reliably detect whether you have all the data.
In viewWillAppear, reset everything. Start loading your data.
If you don't have the data yet, you only display one section with one cell (a placeholder cell that reads "Loading..." and shows a spinner, for instance).
Once the data is completely loaded, you set the bool or whatever.
Call [self.tableView reloadData];
In all of your UITableViewDataSource methods you would need to check whether you've got the data already or not. If not, you return the placeholder data.
[yourtablename reloadData]; will help you relaod the data in the tableview, You can call this once you get the response from your server
I'm not sure there's a "best method" for what you're trying to accomplish here. I would suggest trying the method you have, and seeing if it provides an adequate user experience (whatever that means to you) and if it doesn't, try something else. I would definitely suggest having some sort of "loading" indicator while the table is empty and waiting for http response.
In terms of your question about the "data source", the data source of a UITableView is simply an object that implements the UITableViewDataSource protocol which you can read about here. Often times, you will have XCode set up a UITableViewController object which will act as both delegate and data source to your table view. How you actually store your data is up to you. The data source protocol simply provides the methods by which a table view will "ask" for the data it needs to load.

Core data lazy loading in UITableView - awakeFromFetch called at different points on device and simulator

I am having a problem with core data which seems to be behaving differently on the device and simulator.
I have a very simple UITableView which is driven from a core data model, there are minimal changes from the apple-supplied template.
On the simulator, everything works fine. On the device, I have the correct number of cells in the table view to match the number of managed objects in the database, but the cells themselves contain no data. I have a couple of labels in the cell which are derived from transient properties of the managed object, and these are blank.
I have NSLog statements on the awakeFromFetch method of my managed object subclass, and the cellForRowAtIndexPath method of my table view delegate.
On the simulator, awakeFromFetch is called before cellForRowAtIndexPath, so everything is fine. On the device, it is the other way around, so the cells are blank until I force them to be redisplayed by scrolling up and down.
Why is this happening, and what changes should I make to prevent it? I can see if the managed object is a fault during the cell configuration code, but how do I force the fault to fire? Or should I be doing something else?
I have solved this by ensuring that I configure my cell by calling on a persistent property of the managed object first. This fires the fault and populates the rest of the fields. The transient properties were being populated in awakeFromFetch and this is not called until the fault is fired. Why this is different on the device and simulator, I do not know.
This is obvious that you need to scroll up and down for displaying the cell's value.. Since on iphone the memory is too low to have performance...
There could be one of the solution to use Multithreading or GCD (Grand Central Dispatch).
Use threading and then notify your table that data has been downloaded in memory and ready to show on table view and then update the table...
For reference go through this link

Message users while large table loads

I am processing several large RSS feeds and displaying results in a TableView. The processing starts after the user clicks on the relevant tab. All works well, but as it takes a couple of seconds to process, the NIB and Table don't load until the processing finishes and it looks like the iPhone has seized up. I have added an Activity indicator to the NIB, but because it doesn't load until the table is ready to display, it appears too late to be of any use.
Does anyone have any ideas how to display a message to a user while the table builds/loads? I have tried loading a UIView first and adding the Table as a subview but, again, both seem to load only after the table is ready.
Guidance appreciated.
It's kind of hard to guess what's going on from your description but it looks like your calls aren't asynchronous. Here's what you should be doing in your code:
Make all calls asynchronous. You said your phone is seizing up. Makes it sound like your requests and responses are happening on the main thread. There are many libraries you could use to handle asynchronous calls. ASIHTTPRequest for one example....
Don't wait for the data to come in before displaying the tableView. It's a design principle that you load as much of the UI as possible so that the user has something to look at while your data loads up in the background. What you should be doing is initializing an NSMutableArray to hold the data. Initially this array will contain no objects. This is the array that you use in your data source methods: Use array size for numberOfRowsInSection and use the array objects in cellForRowAtIndexPath. Once your RSS feed XML comes in and is parsed, store that in your arrays and call [tableView reloadData]. This way you don't leave your users looking at a blank screen. (Another good practice is when the array size is zero, show one cell in your tableview that says "data is loading" or something).
When you first initialize and load up your table and then fire off those RSS feed requests, that's where you show an activity indicator view on the tableView. Stop animating the indicator when the RSS data comes in and your tableView reloads.
These are the standard steps you should follow while showing non local data in a tableview. Makes for a smooth user experience.
Like I said before, it seems from your question that your calls are not asynchronous. If I'm wrong, correct me and let's take it from there...

Disable animation of UITableView with NSFetchedResultsController when many rows are being changed

In my UIView I've got a UITableView (UITV) which is controlled by an NSFetchedResultsController (NSFRC). The UIView is inside a UINavigationController.
When the view is about to be loaded/displayed I start some background activities which fetch data from a remote server (JSON) and parse into Core Data.
The NSFRC is being called when the parsing is done and the threaded NSManagedObjectContext have been merged into the main context.
The problem is that sometimes many rows are being inserted to Core Data at once, a lot of table cells are being added and there is quite a delay from that the actual fetching and parsing is done, until the rows are being displayed.
Now I wonder if anyone knows of any solution to, for example:
hook up a spinner to some "fetched results controller inserted all its rows for this time" (or something) notification/delegate call to at least tell the user that "something is going to show up soon"?
Or might the best solution simply be to not initialize the NSFRC until the background fetching and processing is completed?
Thanks!
If I understand your question correctly, you may want to look into the NSFetchedResultsControllerDelegate methods, with documentation available here: http://developer.apple.com/library/ios/#documentation/CoreData/Reference/NSFetchedResultsControllerDelegate_Protocol/Reference/Reference.html
There are delegate methods available for pre changes with controllerWillChangeContent:, post changes with controllerDidChangeContent and during changes with didChangeSection: and didChangeObject.
I hope it helps!
Rog