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.
Related
I have an iPhone app that starts in a table view and goes to a different table view when the user selects a cell. The two table views are in separate classes (or whatever the proper Objective-C term is, ie. 2 different .h and .m files), and the second table view makes a request from a server based on the selection in the first table. There is a noticeable delay and I've been trying to put a UIActivityIndicatorView up when that happens, but that only displays for a split second when view segues to the second table view. I know this is an issue with threading, but I can't get this to work following any of the other posts on this topic. I call my startAnimating in didSelectRowAtIndexPath and the stopAnimating in the viewDidDisappear. I have also tried using the following code to get this to work by calling it in the didSelectRowAtIndexPath: [activityIndicator performSelectorInBackground: #selector(startAnimating) withObject: nil]; How do I make the activity indicator (or any loading animation for that matter) to work when the server request is going on?
I see that this is a frequent problem on Stack Overflow, but I did just find a working solution. I found this forum iphonedevsdk.com/forum/tutorial-discussion/… My issue was a synchronous connection taking up my main thread, so starting the spinner and then calling the connection in a separate method does the trick.
I'm writing an iPad app and have a problem with ui responsiveness / lock up.
I have a UITableView with items, when an item is clicked, it goes out on the internet and fetches that item and displays it. The rest of the program (not shown) will use the item downloaded, so only one item can be downloaded at a time.
Fetching the item can take a long time. During that time I want the user to still be able to scroll around the UITableView, but not to be able to select anything until the previous item clicked has been downloaded.
I know this can be done using threads,blocks, and callbacks, but right now I don't have time to do that (impossible time constraint).
I thought a quick way would be to download it sequentially and use run loops and a flag like this two step process:
When the user clicks on a table cell didSelectRowAtIndexPath is called, in there I set a global flag, then call the download method to download the item. If the user clicks on another item (before the download is completed, it will see the flag checked and exit that function without downloading anything. Basically this:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
static BOOL alreadyInHere=FALSE;
if (alreadyInHere) return;
alreadyInHere=TRUE;
....
downloadItem(...);
ShowAndUseItem(...);
alreadyInHere=FALSE;
}
This allows the user to select only one item at a time.
To allow the user to be able to still scroll around the UITableView during the long download, I put in a run loop in the downloadItem(...) method shown above like this...
-(void) downloadItem(....)
{
BOOL downloading=TRUE;
callFunctionsToStartdownload(...); //
while (downloading) {
downloading=DownloadSomeBytes(...);
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.5, YES);
}
}
The result of (1) and (2) is that the user can still scroll around the UITableView during the sequential download, the alreadyInHere flag prevents them from selecting something and starting another download.
This works in most cases, but around 50% of the time, during the download, the UITableView becomes unresponsive (can't scroll to other items in the table), and even after the download didSelectRowAtIndexPath is never called again when you click on something making theUITableView basically locked up.
My question is, did I do the runLoop correctly?
I know there are other ways to do it, but I have to do it using this general method for now because of other reasons.
Thanks
You should not try to download or do any other potentially long activity in youR tableview didSelectRow method. Exit (return) from this UI method after setting up an asynchronous download so that you don't lock up the UI.
Locking out simultaneous downloads is still ok, but you want be careful to reset state after errors or timeouts.
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.
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...
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