Loading JSON asynchronously and displaying a type of activity indicator

I am loading the JSON channel asynchronously into the App Delegate class. Now the data is loaded for a while, so first an empty view of the table opens, and then it is filled in a few seconds. Therefore, I would like to either:

1- Find out what causes this delay. Therefore, save all the actions in the application: the didFinishLaunchingWithOptions method and load VC only after everything has been loaded.

OR

2 Display the activity indicator until the table fills in the data.

Now in the first scenario, I'm sure that I am pushing the view controller at the wrong time. I tried playing with it, but it seems that this is the only way my application will create and run.

In the second scenario, I would like to know which method "connection" is started first and which last. Therefore, I can start the activity indicator view by the first method and release it at the end of the last method.

Below is my code. Any suggestions / help are appreciated. Thanks for reading.

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { [responseData setLength:0]; } - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { [responseData appendData:data]; } - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error" message:@"Please check your network connection and relaunch the application" delegate:self cancelButtonTitle:@"Dismiss" otherButtonTitles:nil, nil]; [alert show]; [alert release]; } - (void)connectionDidFinishLoading:(NSURLConnection *)connection { NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding]; if ([responseString isEqualToString:@"Unable to find specified resource."]) { NSLog(@"Unable to find specified resource.n"); } else { ListingsViewController *listingsViewController = [[ListingsViewController alloc] initWithNibName:@"ListingsViewController" bundle:nil]; listingsViewController.jsonData = responseString; [self.navigationController pushViewController:listingsViewController animated:NO]; [self.navigationController setViewControllers:[NSArray arrayWithObject:listingsViewController] animated:NO]; [listingsViewController release]; } [connection release]; [responseData release]; } - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Start the HTTP request responseData = [[NSMutableData data] retain]; NSURLRequest *request = [NSURLRequest requestWithURL: [NSURL URLWithString:@"http://www.shoofeetv.com/iphonexml/view/all_channels.json"]]; [[NSURLConnection alloc] initWithRequest:request delegate:self]; // Display the navigation controller self.window.rootViewController = self.navigationController; [self.window makeKeyAndVisible]; return YES; } 
+4
source share
3 answers

Go with option 2. A good user interface design to display things as quickly as possible, even though the user interface may not be usable until the data loads, at least make your app users feel that something going on.

Put the user interface in didFinishLaunchingWithOptions, display an activity indicator and in connectionDidFinishLoading hide and destroy the activity indicator.

I would also recommend wrapping all the asynchronous HTTP request logic in another class and accepting it as a delegate, and then, for example, you could call:

 { // Show activity indicator [httpClient get:@"www.example.com/json" withDelegate:self]; } -(void)httpClientSuccessful:(NSData*)response { // hide activity indicator } 
+2
source

Option 2 is definitely the right way. You should never block the user interface until the network operation is complete. If you have a bad reception, you do not want the application to be inactive for a few seconds. The user will kill him.

NSURLConnection delegate NSURLConnection are called in the order didReceiveResponse , didReceiveData (possibly several times), connectionDidFinishLoading . didFailWithError can be called at any time.

I have successfully used a template in which I immediately create and display a table view. Until the data has been downloaded, I show one cell of the table with an activity indicator and the text "Loading data ...".

Update:

Here is some code (only the main parts). The basic idea is to manage the current state, either not loaded, loaded, failed or ready:

 @interface MyViewController : UITableViewController<NSURLConnectionDelegate> { NSInteger state; NSURLConnection* connection; MyData* data; } ... @end @implementation MyViewController typedef enum LoadingState { eNotLoaded, eLoading, eFailed, eReady } LoadingState; - (void) viewWillAppear: (BOOL) animated { [super viewWillAppear: animated]; // Start loading the data when the table view appears for the first time if (state == eNotLoaded) { NSURLRequest request = // create the request connection = [NSURLConnection initWithRequest:request delegate:self]; [request release]; state = eLoading; } } - (void) connection: (NSURLConnection*) connection didReceiveData: (NSData*) data { // record and process the received data } - (void) connectionDidFinishLoading: (NSURLConnection*) connection { // parse the received data and store it into 'data' [connection release]; connection = nil; // state changed; redisplay the table view state = eReady; [[self tableView] reloadData]; } - (NSInteger) tableView: (UITableView*) tableView numberOfRowsInSection: (NSInteger) section { if (state == eReady) { return [data numberOfRows]; } else { return 1; } } - (UITableViewCell*) tableView: (UITableView*) tableView cellForRowAtIndexPath: (NSIndexPath*) indexPath { static NSString* DefaultCellIdentifier = @"Default"; UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier: DefaultCellIdentifier]; if (cell == nil) { cell = [[[UITableViewCell alloc] initWithStyle: UITableViewCellStyleDefault reuseIdentifier: DefaultCellIdentifier] autorelease]; } if (state == eReady) { cell.textLabel.text = [data labelAtRow: indexPath.row]; if (state == eLoading) { cell.textLabel.text = @"Loading..."; } else { cell.textLabel.text = @"Failed"; } return cell; } 
+2
source

I used the class method NSURLConnection sendAsynchronousRequest: queue: completHandler: which was really useful and simple.

It allows me to send the block as a callback to analyze the error or success for the specified request:

 [NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:url] queue:[NSOperationQueue currentQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) { if (!error) { [self parseData:data]; } else { NSLog(@"ERROR %@", error); } } ]; 

Thus, I can activate the activity indicator before calling the method and delete it after receiving the data, and at the same time, I can handle errors.

I hope this helps;)

0
source

All Articles