I am having a problem in calling web services when the image are being loaded in the tableView with AsynchImageView files .
Below are the steps of my problem:
I call the web service and when it returns the data i reload the UITableView and load all images with AsynchImageView . The web service returns url of images and some text data .
While the images are being loaded , if I call the same web service again then it runs for 30 seconds and then it times out without returning anything but after that time it works fine whenever I call it .
Here is my code for calling services:
-(void)getUserNotificationsPage:(int)page CallBack:(getNotifications)callback{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^ {
#try {
getNotificationsArrayResponseCallback=callback;
NSURLRequest * urlRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:#"%#?token=%#&page=%#",GET_USER_NOTIFICATIONS,[[NSUserDefaults standardUserDefaults]objectForKey:#"token"],[NSString stringWithFormat:#"%i",page]]]];
NSError *err;
NSData *returnData = [NSURLConnection sendSynchronousRequest:urlRequest returningResponse:nil error:&err];
if(err){
returnData=[NSMutableData data];
}
NSString *returnString = [[NSString alloc] initWithData:returnData encoding:NSUTF8StringEncoding];
dispatch_async(dispatch_get_main_queue(), ^ {
id json = [NSJSONSerialization JSONObjectWithData:returnData options:NSJSONReadingMutableContainers error:nil];
NSArray *returnArray=[json objectForKey:#"notifications"];
getNotificationsArrayResponseCallback(returnArray,YES);
});
}
#catch (NSException *exception) {
dispatch_async(dispatch_get_main_queue(), ^ {
UIAlertView *alert=[[UIAlertView alloc]initWithTitle:LANGUAGE(#"Unknown error occurred") message:nil delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
getNotificationsArrayResponseCallback(nil,NO);
});
}
});
}
If I remove the code where the images are being loaded with asynchImageview then I call call the web service at any time and the response is fast at any time .
AsynchImageView *userProfileImageView =[[AsynchImageView alloc] initWithFrameURLStringAndTag:CGRectMake(5, 215, 70, 70) :[NSString stringWithFormat:#"%#%#",SERVER_URL,"some url" ];
[userProfileImageView setBackgroundColor:[UIColor clearColor]];
[userProfileImageView loadImageFromNetwork];
[cell addSubview:userProfileImageView];
As you can see if I comment the line
[userProfileImageView loadImageFromNetwork];
then I can call the web service any number of times and the response is quick but when the asynchimage view is loading the images and then i call the service then it will time out for that time only . For further calling service works fine .
I think this is the issue with threading or calling serveral url requests at the same time .
is this the class you are using? AsynchImageView
If so, it doesn't actually look like his NSURLConnection is being handled in the background. It looks like it's on the main thread.
I have used this other library in the past with success AsyncImageView
If you look in this other library, they are using a proper dispatch queue for running the background. The other class doesn't have that.
dispatch_async(dispatch_get_main_queue(), ^(void) {
Make custom cell and inside of that cell, put this method
-(void)setImageWithUrl:(NSString *)imageUrl
{
NSURL *urlImage = [NSURL URLWithString:imageUrl];
[self.loadingIndicator startAnimating];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_async(queue, ^{
NSError* error = nil;
NSData *imageData = [NSData dataWithContentsOfURL:urlImage options:nil error:&error];
if(error){
dispatch_sync(dispatch_get_main_queue(), ^{
[self.imgVw setImage:[UIImage imageNamed:#"placeholder.png"]];
[self.loadingIndicator stopAnimating];
[[NSURLCache sharedURLCache] removeAllCachedResponses];
});
}else{
UIImage *image = [UIImage imageWithData:imageData];
dispatch_sync(dispatch_get_main_queue(), ^{
[self.imgVw setImage:image];
[self.loadingIndicator stopAnimating];
[[NSURLCache sharedURLCache] removeAllCachedResponses];
});
}
});
}
Now, call this in cellForRowAtIndexPath method. Put it inside cell nil condition, so that it does not called everytime you scroll.
Make comment in case of any doubt.
if(custCellObj == nil)
{
NSString *imgUrl = [yourArray objectAtIndex:indexPath.row];
[custCellObj setImageWithUrl:imgUrl];
}
Related
I have a TableView with customCells, when user press Start button on some cell the loading starts. There are many such cells, so I need to implement this downloading in parallel (asynchronously).
For image downloading and updating the cell in Table view I use next code:
#define myAsyncQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
I include this method into the async queue, that I supposed should enable parallel downloading of images.
- (void)didClickStartAtIndex:(NSInteger)cellIndex withData:
(CustomTableViewCell*)data
{
dispatch_async(myAsyncQueue, ^{
self.customCell = data;
self.selectedCell = cellIndex;
ObjectForTableCell* tmp =[self.dataDictionary objectForKey:self.names[cellIndex]];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:tmp.imeageURL]
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval:60.0];
self.connectionManager = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self];
});
}
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
self.urlResponse = response;
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
NSDictionary *dict = httpResponse.allHeaderFields;
NSString *lengthString = [dict valueForKey:#"Content-Length"];
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
NSNumber *length = [formatter numberFromString:lengthString];
self.totalBytes = length.unsignedIntegerValue;
self.imageData = [[NSMutableData alloc] initWithCapacity:self.totalBytes];
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[self.imageData appendData:data];
self.customCell.progressView.progress = ((100.0/self.urlResponse.expectedContentLength)*self.imageData.length)/100;
float per = ((100.0/self.urlResponse.expectedContentLength)*self.imageData.length);
self.customCell.realProgressStatus.text = [NSString stringWithFormat:#"%0.f%%", per];
}
I tried to set this block to queue - main queue - cause its the place where image is already downloaded,
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
dispatch_async(dispatch_get_main_queue(), ^{
self.customCell.realProgressStatus.text = #"Downloaded";
UIImage *img = [UIImage imageWithData:self.imageData];
self.customCell.image.image = img;
self.customCell.tag = self.selectedCell;
});
[self.savedImages setObject:img forKey:self.customCell.nameOfImage.text];
NSNumber *myNum = [NSNumber numberWithInteger:self.selectedCell];
[self.tagsOfCells addObject:myNum];
}
Without all queues(when I comment it)all works properly - but just 1 downloading at a ones.
But when I tried to implement code with queues as a result it doesn't download anything. I understand that I did smh wrong but I can't define it.
Thanks a lot for any help in advance.
If your looking out for starting it form basics I guess you should start with NSURLSession as NSURLConnection most of implementation had been deprecated and won't be available after iOS 9. For complete reference URL Session Programming Guide and tutorial
Coming back to your question you should do something similar to this took it from tutorial
// 1
NSURLSessionDownloadTask *getImageTask =
[session downloadTaskWithURL:[NSURL URLWithString:imageUrl]
completionHandler:^(NSURL *location,
NSURLResponse *response,
NSError *error) {
// 2
UIImage *downloadedImage =
[UIImage imageWithData:
[NSData dataWithContentsOfURL:location]];
//3
dispatch_async(dispatch_get_main_queue(), ^{
// do stuff with image
_imageWithBlock.image = downloadedImage;
});
}];
// 4
[getImageTask resume];
But my personal recommendation is go for AFNetworking which is best for iOS networking and widely used/tested in iOS app world.
For image download using AFNetworking
[_imageView setImageWithURLRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://i.imgur.com/fVhhR.png"]]
placeholderImage:nil
success:^(NSURLRequest *request , NSHTTPURLResponse *response , UIImage *image ){
NSLog(#"Loaded successfully: %d", [response statusCode]);
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error){
NSLog(#"failed loading: %#", error);
}
];
EDIT : Async downloading using concurrency
// get main dispact queue
dispatch_queue_t queue = dispatch_get_main_queue();
// adding downloading task in queue using block
dispatch_async(queue, ^{
NSData* imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageURL]];
UIImage* image = [[UIImage alloc] initWithData:imageData];
// after download compeletes geting main queue again as there can a possible crash if we assign directly
dispatch_async(dispatch_get_main_queue(), ^{
_imageWithBlock.image = image;
});
});
Use this sample code from Apple to solve your problem of lazy loading.
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
I have some code that gets an image from a web page and displays it in an ImageView. But the image loads very slowly for some reason I don't really understand! Through my logging I can see that all the data for the image (base64 string) arrives pretty instantly, yet it takes about 12 - 15 seconds for the image to appear in the ImageView.
I find this very strange because I used an NSStream to get the data for the image in a different method and the image loaded as soon as all the data arrived. But with this URLSession method its taking longer for the image to load. This doesn't really make sense! This method shouldn't affect how the ImageView loads that data.
Has anybody any ideas why this might be happening?
heres the code:
- (void)postMethod:(NSDictionary *)numDict
{
NSURL *url = [NSURL URLWithString:#"http://theWebAddress.com/aPage.php"]; // add url to page
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
request.HTTPMethod = #"POST";
NSError *error = nil;
NSData *data = [NSJSONSerialization dataWithJSONObject:numDict options:kNilOptions error:&error];
NSLog(#"%#", numDict);
if (!error)
{
NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request fromData:data completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSDictionary *diction = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
for (id key in diction)
{
if ([key isEqualToString:#"text"])
{
NSLog(#"data is text");
self.messageLabel.text = diction[#"text"];
break;
}
else if ([key isEqualToString:#"image"])
{
NSLog(#"data is an image");
// gets the base64 string pretty instantly but takes 12 - 15 seconds to pop up in the imageView
NSData *ImgData = [[NSData alloc] init];
ImgData = [[NSData alloc] initWithBase64EncodedString:diction[#"image"] options:1];
self.ImageView.image = [UIImage imageWithData:ImgData];
break;
}
}
}];
[uploadTask resume];
}
}
many thanks!
Your completion handler might be operating on a background thread. UI updates should always work on the main thread. Put a break point at
self.ImageView.image = [UIImage imageWithData:ImgData];
and see if it is on the main thread. If not, dispatch it to the main thread before you set the ImageView.image:
dispatch_async(dispatch_get_main_queue(), ^{
self.ImageView.image = [UIImage imageWithData:ImgData];
});
You can try to use SDWebImage https://github.com/rs/SDWebImage and all you need is to set the image in imageView like this:
[cell.imageView setImageWithURL:[NSURL URLWithString:#"http://www.domain.com/path/to/image.jpg"]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
You are firstly downloading image and then showing image.You can download image by using lazy loading.
For this you can use EgoImageView not uiimageview.
self.ImageView.imageURL=[NSURL URLWithString:
here self.ImageView is of egoimageview type.
you can get this class from github.
https://github.com/enormego/EGOImageLoading
I have an URL, which when copied into a browser, displays an image. My function is supposed to download the image asynchronously.
- (UIImage *)downloadImage:(NSString *)imageURL
{
NSError *error = nil;
NSURL *urlString = [NSURL URLWithString:imageURL];
NSData *data = [NSData dataWithContentsOfURL:urlString options:NSDataReadingUncached error:&error];
__block UIImage *image;
if (!error) {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
image = [UIImage imageWithData:data];
});
return image;
} else {
NSLog(#"%#", [error localizedDescription]);
}
return nil;
}
When I try to display the image in an UIImageView I get no errors, no nothing. I have NSLogged out both data and the imageURL passed in, and none of those are empty.
Any suggestions?
By calling dispatch_async, you're scheduling that work to happen later. Your function exits with nil before that work is done. You'll want to add a callback block to your function or make it block until you receive and process the image data.
Here is an example of a function with a block callback and how to use it.
- (void)downloadImageAtURL:(NSString *)imageURL withHandler:(void(^)(UIImage *image))handler
{
NSURL *urlString = [NSURL URLWithString:imageURL];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSError *error = nil;
NSData *data = [NSData dataWithContentsOfURL:urlString options:NSDataReadingUncached error:&error];
if (!error) {
UIImage *downloadedImage = [UIImage imageWithData:data];
handler(downloadedImage); // pass back the image in a block
} else {
NSLog(#"%#", [error localizedDescription]);
handler(nil); // pass back nil in the block
}
});
}
- (void)keyboardDidShow:(NSNotification *)aNotification {
[self downloadImageAtURL:#"" withHandler:^(UIImage *image) {
if (image) {
// display
} else {
// handle probelm
}
}];
}
The call to dataWithContentsOfURL:options:error: needs to be within the dispatch_queue block for it to be asynchronous. Any changes to the UI need to be in the mainThread. It should look something like this:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^(void) {
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
UIImage* image = [[UIImage alloc] initWithData:imageData];
if (image) {
dispatch_async(dispatch_get_main_queue(), ^{
//load image into UIImageView
}
});
}
});
You are not downloading the image asynchronously. Moreover, a method that is supposed to return a value in an async way cannot return that value through the method return value, but it should return it using a block.
You can try to do something like this:
- (void)downloadImage:(NSString *)imageURL onComplete:(void (^)(UIImage *, NSError * error))onComplete
{
NSURL *urlString = [NSURL URLWithString:imageURL];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSError *error = nil;
NSData *data = [NSData dataWithContentsOfURL:urlString options:NSDataReadingUncached error:&error];
image = [UIImage imageWithData:data];
if (onComplete) {
// Keep in mind that onComplete block will be called on a background thread.
// If you need to use it on UIImageView, you must set it on main thread.
onComplete(image, error);
}
});
}
Then, when you need to set the UIImageView image:
__weak typeof(self)selfB = self; // Better to use a weak reference inside blocks to avoid retain cycles
[self downloadImage:myURLString onComplete:^(UIImage * image, NSError * error) {
dispatch_async(dispatch_get_main_queue(), ^{ // As you can see, we use main thread for UI updates
selfB.imageView.image = image;
});
}];
In my code I am calling this
[request performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
if (responseData != nil) {
NSError *error = nil;
NSArray *dataSource = [NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingMutableLeaves error:&error];
[self.ticker1 loadData:dataSource];
} else {
NSLog(#"Error requesting timeline %# with user info %#.", error, error.userInfo);
}
}];
and in the loadData: method I do this
NSDictionary *dict = [dataSource objectAtIndex:0];
_label.text = [dict objectForKey:#"text"];
dispatch_queue_t queue = dispatch_queue_create("com.test.ios.task", NULL);
dispatch_queue_t main = dispatch_get_main_queue();
dispatch_async(queue, ^{
NSURL *imageURL = [NSURL URLWithString:[[dict objectForKey:#"user"] objectForKey:#"profile_image_url"]];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
dispatch_async(main, ^{
_image.image = [UIImage imageWithData:imageData];
});
});
dispatch_release(queue);
The problem is the image I load on main queue loads much faster than _label.text is being set. It gets set after a long delay of about 4-5 seconds. I would like to know why this is happening. Is it because the main queue is not being released or something on that lines?
I can't tell from your code which thread the request's handler block is being called on, but if it's not the main thread, that could be the problem.
Try setting the label's text property from within a
dispatch_async(main, ^{
_label.text = [dict objectForKey:#"text"];
});
block.
The weirdest thing fixed this problem for me. I made the label hight fixed and slightly bigger then it should be, in the storyboard. It made a yellow warning in the constraints, but at least the label is loading.