Adding UIActivityIndicatorView on Synchronous Web Service data and Populating UITableView - ios

I am fetching the data from a web service by synchronous method. I make the request to the web service then view freezes. I try to add the UIActivityIndicatorView before loading the data from the web service and stopped it after getting the data but activity indicator is not displayed.
I tried to put the web service data fetch operations on the different thread
[NSThread detachNewThreadSelector:#selector(fetchRequest) toTarget:self withObject:nil];
but at this time TableView crashes as it does not get the data for drawing the cells.
in fetchRequest function I am doing
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL
URLWithString:URLString]];
NSData *response = [NSURLConnection sendSynchronousRequest:request
returningResponse:nil error:nil];
NSError *jsonParsingError = nil;
NSDictionary *tableData = [NSJSONSerialization JSONObjectWithData:response
options:0
error:&jsonParsingError];
responseArray = [[NSMutableArray alloc]initWithArray:[tableData objectForKey:#"data"]];
for(int i = 0; i < responseArray.count; i++)
{
NSArray * tempArray = responseArray[i];
responseArray[i] = [tempArray mutableCopy];
}
This responseArray is used to fill the information in the cell
Please tell me how to do this. Any help will be appreciated ...

The problem lies in your very approach. Synchronous methods run on the main thread. And because the UI updates on the main thread, your app hangs.
So, the solution would be using an asynchronous method to download the data on a separate thread, so that your UI won't hang.
So, use the NSURLConnection's sendAsynchronousRequest. Here's some sample code :
NSURL *url = [NSURL URLWithString:#"YOUR_URL_HERE"];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:urlRequest queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
//this is called once the download or whatever completes. So you can choose to populate the TableView or stopping the IndicatorView from a method call to an asynchronous method to do so.
}];

You should better use Grand Central Dispatch to fetch the data like this so you dispatch it in a background queue and do not block the main thread which is also used for UI updates:
dispatch_queue_t myqueue = dispatch_queue_create("myqueue", NULL);
dispatch_async(myqueue, ^(void) {
[self fetchRequest];
dispatch_async(dispatch_get_main_queue(), ^{
// Update UI on main queue
[self.tableView reloadData];
});
});
Regarding the Activity indicator you can use in the start of the parsing:
[self.activityIndicator startAnimating];
self.activityIndicator.hidesWhenStopped = YES
And then when your table is filled with data:
[self.activityIndicator stopAnimating];

Related

Update UiTableview with data getting from url

In my application I'm implementing UITableView with data fetching from URL.
Everything goes fine.But the problem here is the data coming from backend url is too large. So I am showing UIActivityIndicatorView until data fetching completed.
Because of large data it's taking mostly 3 or 4 minutes to fetching. So how to update UITableView with data getting from url as soon as possible.
I'm using asynchronousRequest to do it.
I used this code in cellForRowAtIndexPath
NSString *post = [NSString stringWithFormat:#"skey=%#&user_id=%#",#"XXXXXX",#"3225"];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:[NSURL URLWithString:[NSString stringWithFormat:#"http://chkdin.com/dev/api/peoplearoundmexy/?%#",post]]];
[request setHTTPMethod:#"POST"];
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:nil];
NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (data) {
id json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
NSMutableArray *designation=[json valueForKey:#"designation"];
UIImage *imageobj1=[UIImage imageNamed:#"userpic.jpg"];
NSData *imagedata = UIImagePNGRepresentation(imageobj1);
dispatch_async(dispatch_get_main_queue(), ^{
PeopleNearbyCell *updateCell = (id)[collectionview cellForItemAtIndexPath:indexPath];
if (updateCell)
cell.UserProfilePic.image = [UIImage imageWithData:imagedata];
});
}
}];
[task resume];
But it's not working. How can I achieve this?
The network request is asynchronous processing, cellforrowatindexpath is in the main thread, if you need to refresh the table in the network request, you need to write a network request method, and then get the data, in which the method of assignment, then refresh the table.
You can use lazyload method. This code in the cellForRow delegate.
[cell.loadActivity startAnimating];
dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_async(backgroundQueue, ^{
// YOUR LOAD FROM URL METHOD HERE.
// only update UI on the main thread
dispatch_async(dispatch_get_main_queue(), ^{
if (img==nil) {
// HANDLE WHEN IMAGE RETRIEVE FAILED
} else {
// ASSIGN IT
cell.UserProfilePic.image = img;
}
[cell.loadActivity stopAnimating];
});
});

How to display UIActivityIndicatorView while fetching JSON data to be populated in UITableView?

I have a problem with my application.It freeze for several second when I tap the sidebar menu.
What happen when I tapped menu is I pass string that gonna be url for json data fetch in my mainviewcontroller.Then it freeze because I fetch the data and populating data in tableview.
However I really new to ios programming,I wonder how can I remove the freeze?.
thanks in advance
here is my code snippet for the mainviewcontroller:
Don't use dataWiyhContentsOfURL:, or at least not directly on the main thread. If you block the main thread then the whole app stops working (as you see).
You need to learn about background threads and callback blocks, and look at using NSURLSession to download your data and then process it.
Instead of using dataWithContentsOfURL (which will block the main thread and so the UI) you need to start an asynchronous connection. In the IF ELSE change the two requests to something like below. The completionHandler (Block) is executed when done, the data parsed, HUD removed and table Updated.
You can even (and in fact must) do this within your cellForRowAtIndexPath for each of the images, however, I would use SDWebImage as it has a cache and is very easy to use.
There are also other methods if this is not right for you such as NSURLSession.
Some other points;
I have also noted that the HUD is stopped on every iteration of the FOR and probably should be outside.
I also can not see how your data is being loaded so I added a [myTable reloadData];
I can not see that the "dictionary" object is needed as it can be added directly to the array (see code)
// If you have the status bar showing
// [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[HUD showUIBlockingIndicatorWithText:#"Please wait. . ."];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:kategori]];
[request setTimeoutInterval: 10.0];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue currentQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
// [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
if (data != nil && error == nil)
{
//All Worked
id jsonObjects = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
for (NSDictionary *dataDict in jsonObjects)
{
NSString *title_data = [dataDict objectForKey:#"title"];
NSString *thumbnail_data = [dataDict objectForKey:#"thumb"];
NSString *author_data = [dataDict objectForKey:#"creator"];
NSString *link_data = [dataDict objectForKey:#"link"];
[myObject addObject:[[NSDictionary alloc]initWithObjectsAndKeys:
title_data, title,
thumbnail_data, thumbnail,
author_data,author,
link_data,link,
nil]];
}
[HUD hideUIBlockingIndicator];
[myTableView reloadData];
}
else
{
// There was an error
}
}];
For the images something like (this is not tested). I am not sure what format your images are in but you should be able to just add it, this may need tweeking;
cell.imageView.frame = CGRectMake(0, 0, 80, 70);
__block UIImageView *cellImage = cell.imageView;
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[tmpDict objectForKey:thumbnail]]];
[request setTimeoutInterval: 10.0];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue currentQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
if (data != nil && error == nil)
{
//All Worked
cellImage.image = [[UIImage alloc]initWithData:data];
[cellImage layoutIfNeeded];
}
else
{
// There was an error
}
}];
You can start activity indicator and call fetch data method after few time...
- (void)viewDidLoad{
[activityIndicator startAnimating];
[self performSelector:#selector(fetchData) withObject:nil afterDelay:0.5];
}
- (void)fetchData{
Fetch your data over here
}
Or ideally you have to load data Asynchronous
For loading data Asynchronously check out the following link-
iphone-synchronous-and-asynchronous-json-parse
I Prefer MBProgressHUD.
Here is the link for 3rd Party API.
https://github.com/jdg/MBProgressHUD
Just copy these two files in your app.
MBProgressHUD.h
MBProgressHUD.m

iOS: NSOperationQueue: Secondary queue

I would like to receive some data to the server in an asynchronous way and avoiding to overload the App UI performance. Hence would like to send tasks to the secondary queue and not the main one.
This is my current solution which uses the "main queue" ([NSOperationQueue mainQueue] which I understand slows down the performance):
-(NSDictionary*) fetchURL:(NSString*)url
{
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:kCONTACTSINFOURL]];
__block BOOL hasError = FALSE;
__block NSDictionary *json;
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError)
{
//Verify type of connection error
json = [NSJSONSerialization JSONObjectWithData:data
options:0
error:nil];
NSLog(#"Async JSON: %#", json);
}];
if (hasError) {
[[NSOperationQueue mainQueue] suspend];
return nil;
}
return json;
}
In order to use the secondary queue, and avoid overloading the UI and App performance, is it ok to allocate a shared NSOperationQueue and refer to that? Or is there some other "better" class or method to achieve this?
This would be my improved solution using a secondary NSOperationQueue:
Creating a secondary queue:
NSOperationQueue* otherQueue = [NSOperationQueue init];
Using the other (secondary) queue:
....
[NSURLConnection sendAsynchronousRequest:request
queue:otherQueue
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError)
....
}
Is this correct? Or is there any other way to deal with this?
... following on from the comments...
The NSURLConnection method
[NSURLConnection sendAsynchronousRequest:queue:completionHandler:]
allows you to specify the queue that the completion handler will be called on when the connection is complete. If you suspend this queue you will not stop the requests being sent, you will just stop getting callbacks when they are done. You may want to think about doing this slightly differently...
NSOperation is great, but I tend to prefer going straight for GCD (NSOperation is just a nice obj-c wrapper on the top) if you are hard-set on using NSOperations let me know and i'll add some advice for that.
I assume that you have some kind of manager class that handles all of your server communication? If not, I would recommend that you do, and have it as a singleton.
# interface ChatManager : NSObject
+ (ChatManager *)sharedManager;
#end
#implementation ChatManager {
dispatch_queue_t fetchQueue;
dispatch_queue_t postQueue;
}
+ (ChatManager *)sharedManager {
// This is just the standard apple pattern for creating a singleton
static SCAddressBookManager *sharedManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedManager = [[SCAddressBookManager alloc] init];
});
return sharedManager;
}
- (id)init {
self = [super init];
if (self) {
// Create a dispatch queue that will run requests one after another, you could make this concurrent but that may cause messages to be lost when you suspend
fetchQueue = dispatch_queue_create("fetch_queue", DISPATCH_QUEUE_SERIAL);
postQueue = dispatch_queue_create("post_queue", DISPATCH_QUEUE_SERIAL);
}
}
- (NSDictionary *)fetchURL:(NSURL *)url {
// In here we will dispatch to our queue and so that the
dispatch_async(fetchQueue, ^{
NSURLRequest = // create your request
NSURLResponse *response = nil;
NSError *error = nil;
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
// Check for error
if (error) dispatch_suspend(fetchQueue); // this suspends the queue that is making the calls to the server, so will stop attempting to send messages when you have an error - you should start pinging to see when you come back online here too, and then use dispatch_resume(fetchQueue) to get it going again!
else {
json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(#"Async JSON: %#", json);
return json; // ??
}
});
}
- (NSDictionary *)postURL:(NSURL *)url data:(NSData *)bodyData {
// In here we will dispatch to our queue and so that the
dispatch_async(postQueue, ^{
/*
Make your post request.
*/
// Check for error
if (error) dispatch_suspend(postQueue);
else {
json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(#"Async JSON: %#", json);
return json; // ??
}
});
}
#end
You may also need to use
dispatch_sync(dispatch_get_main_queue(), ^{
// Do stuff on the main thread here
};
if you need to make delegate style callbacks to the main thread!
This is a basic idea on how I would have things setup to try and achieve what you are going for... feel free to let me know if any of this doesn't make sense to you..?
As a side note, I have assumed that for whatever reason you need to have a simple http interface for your server. The much better approach would be to have a persistent socket open between the app and your server, and then you can push data up and down at will. A socket with a heartbeat would also let you know when your connection has gone down. Not sure if maybe you would like me to elaborate on this option some more...

Alert on concurrent thread

In my application I'm calling a method asynchronously by a button press. The screen segues to a different view controller which is a table view.
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDownloadTask *task = [session downloadTaskWithRequest:request
completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error)
{
NSData *data = [[NSData alloc] initWithContentsOfURL:location];
NSArray *array = [NSJSONSerialization JSONObjectWithData:data
options:0
error:&error];
self.yearBucket = [NSMutableArray array];
for (NSDictionary * dict in array) {
Year *year = [[Year alloc ]init];
year.yearName =[dict objectForKey:#"Year"];
year.speeches = [dict objectForKey:#"Speeches"];
[self.yearBucket addObject:year];
}
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}
...
The problem is that the UI moves into a blank table view, since the data takes time to load. How can I display and alert view on the next screen that dynamically displays the loading of the data and can is dismissed as soon as the view refreshes back to the main thread.
I would suggest you display an MVProgressHUD on your view whilst you load the data and then perform a [tableview reloadData] in order to update the data shown in the table.
You should probably also display a network activity indicator. This is simply good practise when network connectivity is occurring. I presume if your app relies on an internet connection you are checking for connectivity before allowing the user to begin a request?

Setting UILabel text taking longer than expected iOS7

In my view I attempt to display some weather related info. I use
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
Make NSURL and other related things
Send NSURLRequest
Set 2 UILabels to data
NSLog(Show data retrieved)
});
For some reason, I see the NSLog line roughly 15-45 seconds before the uilabel changes to the new text. I am fairly new to Obj-C and a lot of my code comes from using tutorial so I dont have the greatest understanding of the dispatch_async method. Any thoughts or suggestions would be appreciated.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURLResponse *response = nil;
NSError *error = nil;
NSData *respData = nil;
NSURLRequest *forecastRequest = [NSURLRequest requestWithURL:currentForecastUrl];
respData = [NSURLConnection sendSynchronousRequest:forecastRequest returningResponse:&response error:&error];
[_responseData appendData:respData];
NSLog(#"Response: %#", response);
NSLog(#"Error: %#", error);
id JSON = [_responseData yajl_JSON];
currentForecast = [[NSArray alloc]initWithObjects:[JSON objectForKey:#"temperature"],[JSON objectForKey:#"feelsLike"],nil];
[_temperatureLabel setText:[NSString stringWithFormat:#"%#",[JSON objectForKey:#"temperature"]]];
[_tempDescriptionLabel setText:[NSString stringWithFormat:#"%#",[JSON objectForKey:#"desc"]]];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:2.0];
[_tempDescriptionLabel setAlpha:1];
[_temperatureLabel setAlpha:1];
[UIView commitAnimations];
NSLog(#"Done");
NSLog(#"JSON: %#", [JSON yajl_JSONStringWithOptions:YAJLGenOptionsBeautify indentString:#" "]);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
is used for create a background thread, but the user interface should be updated in the Main Thread
like this:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//do stuff in background
dispatch_async(dispatch_get_main_queue(), ^{
// Update your UI
});
});
All the "use the main queue" answers are good. But please notice that NSURLConnection does all this for you.
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
// this happens on the main thread because we passed mainQueue
}];
EDIT: Here's your code, except smaller and with modern syntax. It should do exactly the same job. For fun, cut and paste this in.
// it assumes the following variables in scope
NSURL *currentForecastUrl;
__block NSArray *currentForecast;
UILabel *_temperatureLabel, *_tempDescriptionLabel;
// start counting lines here
NSURLRequest *forecastRequest = [NSURLRequest requestWithURL:currentForecastUrl];
[NSURLConnection sendAsynchronousRequest:forecastRequest queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
// I cheated a little on lines, because we really ought to check errors
// in here.
NSError *parseError; // use native JSON parser
id parse = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&parseError];
currentForecast = #[parse[#"temperature"], parse[#"feelsLike"]]; // note the array and dictionary literal syntax
_temperatureLabel.text = parse[#"temperature"]; // if these are ivars, I reccomend using self.temperatureLabel, not _temp...
_tempDescriptionLabel.text = parse[#"desc"];
[UIView animateWithDuration:2.0 animations:^{ // use UIView animation block
_temperatureLabel.alpha = 1.0;
_tempDescriptionLabel.alpha = 1.0;
}];
}];
Deleting code for me is the most pleasurable part of programming. The line of code that's easiest to read, executes the quickest and requires no testing is the line that isn't there.
Your problem is because you're trying to update the UILabels off the main thread. I've seen that exact symptom before, where updating UI elements off the main thread still updates them, but with a long delay. So any time you have a UI element that's behaving in this way, check to make sure it's executing on the main thread.
I am surprised this code works at all because you are updating the label on a background thread. You want to do something along these lines:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul), ^{
//Make NSURL and other related things
//Send NSURLRequest
dispatch_async(dispatch_get_main_queue(), ^{
// Set your UILabels
});
});

Resources