Message users while large table loads - iphone

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...

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.

Preload data in a user friendly way

My application consists of data downloaded from an XML file. data contains short text and images.
currently I'm downloading up all data and building up the view in a view controller, in the ViewDidLoad method, which causes the application not to show up the root view until all data is downloaded. I want it to show up in a more user friendly way, at least to preload some of the data during the splash screen.
By the way I've done the lazy image loading so images can load while the main view is displayed.
As long as the number of views depend on number of rows in XML, loading XML asynchronously while building up the the view does not suite my need (or maybe I'm wrong).
I understand that describing the solution in an answer is quite a challenge, so maybe you could point to an article or even a book that has a detailed explanation of asynchronous and multithread handling.
I don't see why you can't load it asynchronously o_O
You should show some "Loading" view anyway (preferably with an activity Indicator)
A progress bar would be nice, too.
And when it's done downloading you just reload and relayout your view.
If by "number of views" you mean the number of cells in a table row you can just tell the tableview to reload all data whereas your numberOfRowsInSection function (or whatever you want to use) should return appropriate values depending on whether it's loading or not.
EDIT: you shouldn't do that while the application is still loading because that's extremely user-unfriendly and slows down the loading of the application aswell
I think you should parse all data in didFinishLaunchingWithOptions: method of appDelegate and then use following methods to do parsing.
[self performSelectorInBackground:#selector(downloadData:) withObject:nil];
method downloadData: contain parsing procedure.

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.

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

iPhone/Cocoa Touch - problem with multithreading (Stanford Presence 3 project)

I have been solving the Stanford free iPhone course project called Presence 3 (found on the stanford site: www.stanford.edu/class/cs193p/cgi-bin/downloads.php, which pulls data from twitter for users, which are stored in a plist. A UIActivityIndicator (spinner) is visible while the data is loading. Once the data has been loaded, a TableView displays the users in a list with their photos, and user statuses show up when a user's cell is clicked. I can successfully display the TableView with the photos and bring up another view controller with statuses when clicked. But when I add in the spinner, my program crashes. I set up my program almost identically to the ThreadedFlickrTableView example project, which can also be found at the same link above (sorry, I'm a new user and can only post one link), which works. I put breakpoints in my code to see where the problem was, and I found that the program crashes when it is loading a cell in the cellForRowAtIndexPath method, specifically when it is retrieving the photo from the appropriate array (followeesPhotoURLs). This is because the array is empty - the photos were never downloaded since the main thread decides to execute the cell-loading method before the thread dedicated to downloading from the internet finishes executing (it does start executing).
I looked on the auditors discussion group page for the course and found that someone else had the same problem, but the thread never resolved the issue, and I emailed to no avail:
http://groups.google.com/group/iphone-appdev-auditors/browse_thread/thread/ccfc6ae99b4cf45d/ef1b8935e749c7c2?hl=en&lnk=gst&q=presence3#ef1b8935e749c7c2
My first rule of UITableView is never report sections or rows that aren't ready (with something, even if only a placeholder) because it will crash every time.
The spinner is spinning while the resource is loading. So you are waiting for a resource that may or may not be ready because you don't know the exact state of your background process. How about setting a value in your main thread indicating that things are not ready. Then when your secondary thread finishes loading things you can do performSelectorOnMainThread to cause some main thread function to set the value to indicate you can proceed. Until the value says proceed, your main thread does not try to access those values the secondary thread might be touching. Maybe your cells will display "loading" or similar until the data is ready, or you will just add cells as they become ready.
One more thing - ONLY the main thread can touch the UI. Nothing in UIKit is thread safe unless explicitly stated. The changes to the progress indicator must be handled by the main thread, it should start the indicator and stop it (probably when your secondary thread notifies "done", as above).
I just finished working through this today and here's the sequence of events that I observed:
Your table view will load up with nothing because your arrays contain nothing
Your thread will go and retrieve Twitter data
Your table view will be refreshed with the Twitter data [self.tableView reloadData]
If you're like me, you try to set the user name using something similar to
cell.textLabel.text = [[userInfo objectAtIndex:indexPath.row] valueForKey:#"name"];
I think that because it is very specific, the application really tries to look for it and doesn't return null if it doesn't find anything, unlike the code in the example which is
cell.text = [photoNames objectAtIndex:indexPath.row];
...so your application errors out the first time it tries to load data with empty arrays.
The way I worked around this is to create an array that loads the user names from the property list at the very beginning so I know how many entries there should be in my array of content. The key part is to create a condition before setting up your cell so that you know you have all the information that you need such as...
// Set up the cell...
if ([userInfo count] == [userList count]) {
userInfo is my array of dictionaries with the data that comes from Twitter.
userList is my array of values from the property list.