Using blocks to download image, freezing UI - ios

In my app I am downloading image using blocks but it is freezing my UI. I have one network class which contains method to download image,
-(void)downloadImageWithCompletionHandler:^(NSData *aData, NSError *error)aBlock;
I am calling above method in my view controller to download image. So once the image is downloaded I am using NSData to show in image view. The network class method uses NSURLConnection methods to download the image.
[[NSURLConnection alloc] initWithRequest:theURLRequest delegate:self];
Once the data download is complete I am calling completion handler block of the view controller.
But I am not sure why my UI is freezing? Can anyone help me find where I am doing wrong?
Thanks in advance!

- (void) setThumbnailUrlString:(NSString *)urlString
{
NSString *url= [NSString stringWithFormat:#"%#",urlString];
//Set up Request:
NSMutableURLRequest *request = [[NSMutableURLRequest alloc]init];
[request setURL:[NSURL URLWithString:url]];
NSOperationQueue *queue=[[NSOperationQueue alloc] init];
if ( queue == nil ){
queue = [[NSOperationQueue alloc] init];
}
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse * resp, NSData *data, NSError *error)
{
dispatch_async(dispatch_get_main_queue(),^
{
if ( error == nil && data )
{
UIImage *urlImage = [[UIImage alloc] initWithData:data];
_headImageView.image=urlImage;
_backgroundImageView.image=urlImage;
}
});
}];
}

You need to download the image in background thread to avoid freezing the UI thread.There is a simple demo to achieve this.
- (void)downloadImageWithCompletionHandler:(void(^)(NSData *aData, NSError *error))aBlock {
NSURLRequest *theURLRequest = nil; // assign your request here.
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
[NSURLConnection sendAsynchronousRequest:theURLRequest queue:mainQueue completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
// UIThread.
aBlock(data,connectionError);
}];
}
how to call this method.
[self downloadImageWithCompletionHandler:^(NSData *aData, NSError *error) {
// get UIImage.
UIImage *image = [UIImage imageWithData:aData];
}];

I figured out the problem. Problem was not in the block or using NSUrlConnection method, it is working properly. Problem was, I was saving data in file once I download it. This operation was happening on main thread which was blocking the UI.

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

NSURLConnection async not working

I'm trying to make an asynchronous NSURL Request, but I'm getting all "FALSE."
-(BOOL)checkConnectionForHost:(NSString*)host{
BOOL __block isOnline = NO;
NSURLRequest *request = [[NSURLRequest alloc]initWithURL:[NSURL URLWithString:host] cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:1];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if([(NSHTTPURLResponse*)response statusCode]==200){
isOnline = TRUE;
}
}];
NSLog(#"%i",isOnline);
return isOnline;
}
Also, this code is being called "6" times when I'm actually just using it with a:
-(UICollectionViewCell*)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
and there are only 3 cells, or 3 items in my data source. First time dealing with async and callbacks in Objective-C, so a detailed answer would be much appreciated! Thanks!
Asynchronous calls will be executed in parallel, and its result will receive in the completion block. In your case, the return statement will be executed before the completion of the Asynchronous request. That will be always FALSE.
You should use Synchronous request for this, and handle not to Block the UI.
-(BOOL)checkConnectionForHost:(NSString*)host{
BOOL isOnline = NO;
NSURLRequest *request = [[NSURLRequest alloc]initWithURL:[NSURL URLWithString:host] cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:1];
NSHTTPURLResponse *response;
NSError *error;
NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
NSLog(#"Response status Code : %d",response.statusCode);
isOnline = response.statusCode == 200;
return isOnline;
}
You can use that method inside dispatch queues,
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);
dispatch_async(queue, ^{
BOOL status = [self checkConnectionForHost:#"http://google.com"];
NSLog(#"Host status : %#",status ? #"Online" : #"Offline");
});
You should realize that this problem is inherently asynchronous. You can't solve it with a synchronous approach. That is, your accepted solution is just an elaborated and suboptimal wrapper which ends up being eventually asynchronous anyway.
The better approach is to use an asynchronous method with a completion handler, e.g.:
typedef void (^completion_t)(BOOL isReachable);
-(void)checkConnectionForHost:(NSString*)host completion:(completion_t)completionHandler;
You can implement is as follows (even though the request isn't optimal for checking reachability):
-(void)checkConnectionForHost:(NSString*)host
completion:(completion_t)completionHandler
{
NSURLRequest* request = [[NSURLRequest alloc]initWithURL:[NSURL URLWithString:host]];
[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (completionHandler) {
completionHandler(connectionError == nil && [(NSHTTPURLResponse*)response statusCode]==200);
}
}];
}
Please note:
Don't set a timeout as short as in your original code.
The completion handler will be called on a private thread.
Usage:
[self checkConnectionForHost:self.host completion:^(BOOL isReachable){
dispatch_async(dispatch_get_main_queue(), ^{
self.reachableLabel.text = isReachable ? #"" : #"Service unavailable";
});
}];
Your isOnline is probably being set to YES, but it's happening asynchronously. It is almost certainly executing after you log out the value of isOnline. So you should move your NSLog() call up into the block you pass as the handler to the asynchronous URL request.

NSURLConnection taking a long time

This code loads a table view:
- (void)viewDidLoad
{
[super viewDidLoad];
//test data
NSURL *url =[[NSURL alloc] initWithString:urlString];
// NSLog(#"String to request: %#",url);
[ NSURLConnection
sendAsynchronousRequest:[[NSURLRequest alloc]initWithURL:url]
queue:[[NSOperationQueue alloc]init]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if([data length] >0 && connectionError ==nil){
NSArray *arrTitle=[[NSArray alloc]init];
NSString *str=[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
arrTitle= [Helper doSplitChar:[Helper splitChar20] :str];
self.tableView.delegate = self;
self.tableView.dataSource = self;
[self fecthDataToItem:arrTitle];
[self.tableView reloadData];
NSLog(#"Load data success");
}else if (connectionError!=nil){
NSLog(#"Error: %#",connectionError);
}
}];
// arrTitle = [NSArray arrayWithObjects:#"ee",#"bb",#"dd", nil];
}
And it takes 10 - 15s to load. How can I make this faster?
.
Thanks Rob and rmaddy, problem is solve.
As rmaddy points out, you must do UI updates on the main queue. Failure to do so will, amongst other things, account for some of the problems you're experiencing.
The queue parameter of sendAsynchronousRequest indicates the queue upon which you want the completion block to run. So, you can simply specify [NSOperationQueue mainQueue]:
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if([data length] > 0 && connectionError == nil) {
NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSArray *arrTitle = [Helper doSplitChar:[Helper splitChar20] :str];
self.tableView.delegate = self;
self.tableView.dataSource = self;
[self fecthDataToItem:arrTitle];
[self.tableView reloadData];
} else if (connectionError!=nil) {
NSLog(#"Error: %#",connectionError);
}
}];
Or, if you where doing something slow or computationally expensive/slow within that block, go ahead and use your own background queue, but then dispatch the UI updates back to the main queue, e.g.:
[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
// do something computationally expensive here
// when ready to update the UI, dispatch that back to the main queue
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// update your UI here
}];
}];
Either way, you should always do UI updates (and probably model updates, too, to keep that synchronized) on the main queue.

NSURLConnection sendAsynchronousRequest for image crashes in ios 4

Hi i am using following code to load the image using NSURLConnection SendAsynchronousRequest call for Tableview but it crashes for IOS 4.3 but same code works for IOS 5.
So can anyone please tell me what changes i have to do support for IOS 4.3
i have gone through below links but nothing worked for me.
NSURLConnection sendAsynchronousRequest:queue:completionHandler not working in iOS 4.3
Have a class called
imagefetcher.h
- (void)fetchImageForURL:(NSURL *)url atIndexPath:(NSIndexPath *)indexPath inTableView:(UITableView *)table;
imagefetcher.m
- (void)fetchImageForURL:(NSURL *)url atIndexPath:(NSIndexPath *)indexPath inTableView:(UITableView *)table {
// NOTE: url is just relative
// There is an issue on iOS 5 that causes the memory capacity to be set to 0 whenever a UIWebView is
// used for the first time. This will correct that issue.
NSLog(#"in fetchImageForURL %#",url);
if([[NSURLCache sharedURLCache] memoryCapacity] != URLMemoryCachSize)
{
[[NSURLCache sharedURLCache] setMemoryCapacity:URLMemoryCachSize];
}
NSURLRequest *request = [NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:30.0f];
NSCachedURLResponse *cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:request];
if (cachedResponse)
{
NSData *data = [cachedResponse data];
NSLog(#"from cache");
[self postImageCallbackWithTableView:table atIndexPath:indexPath forURL:url withImageData:data];
}
else
{
returningResponse:&response error:&error];
// NSLog(#"loading synchronously");
[NSURLConnection sendAsynchronousRequest:request
queue:fetcherQueue
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
[self postImageCallbackWithTableView:table atIndexPath:indexPath forURL:url withImageData:data];
}];
// [self postImageCallbackWithTableView:table atIndexPath:indexPath forURL:url withImageData:data];
}
}
in tableview controller i am calling follwing method but it crsahes for IOS 4.3 but same works for IOS 5
tableviewcontroller.m
-viewdidload()
{
[NSURLConnection sendAsynchronousRequest:request queue:fetcherQueue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
//[self postImageCallbackWithTableView:table atIndexPath:indexPath forURL:url withImageData:data];
UIImage *image = [UIImage imageWithData:data];
[self.images setObject:image forKey:index];
[table1 beginUpdates];
[table1 reloadRowsAtIndexPaths:#[index] withRowAnimation:UITableViewRowAnimationNone];
[table1 endUpdates];
}];
}
If you look at the documentation for sendAsynchronousRequest, it requires iOS 5. If you need to support iOS 4.3, you'll have to use connectionWithRequest:delegate: or initWithRequest:delegate: and then implement the NSURLConnectionDataDelegate methods (which, while a little more work, offers other advantages such as being able to monitor the progress or cancel the request if you need).
Or, as the answer provided at that other question suggests, write your own method that provides the sendAsynchronousRequest functionality but that actually calls sendSynchronousRequest.
Or, just replace your call with sendAsynchronousRequest:
[NSURLConnection sendAsynchronousRequest:request queue:fetcherQueue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
// do something with `data`, `error`, and `response`
}];
With a call to sendSynchronousRequest that you'll perform on some NSOperationQueue queue. So, first, define a property for your operation queue:
#property (nonatomic, retain) NSOperationQueue *networkQueue;
And then initialize it, for example in viewDidLoad:
self.networkQueue = [[NSOperationQueue alloc] init];
self.networkQueue.name = #"com.domain.app.networkqueue";
self.networkQueue.maxConcurrentOperationCount = 4;
And then you can use that network operation queue to call sendSynchronousRequest:
[self.networkQueue addOperationWithBlock:^{
NSURLResponse *response = nil;
NSError *error = nil;
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
// assuming you want to interact with your UI and or synchronize changes to your model, dispatch this final processing back to the main queue
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// do something with `data`, `error`, and `response`
}];
}];
Bottom line, just replace your calls to sendAsynchronousRequest with methods, such as sendSynchronousRequest, that were available in iOS 4.3.

Resources