Multiple async requests at once in objective c - ios

I have am downloading multiple images from a web server at the same time, and I the problem is they are getting mixed up with each other.
dispatch_async(dispatch_get_global_queue(0,0), ^{
NSData * data = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:#"website.com"]];
if (data == nil)
return;
dispatch_async(dispatch_get_main_queue(), ^{
cell.imageView.image = [UIImage imageWithData:data];
});
});
I got this code from: Getting Image from URL Objective C.
How do i fix this?? also is there any way to speed up download speeds?

You can use "AsyncImageView" class files it will load image synchronously and it shows the activity indicator while image loading
AsyncImageView is the class file in which it will create connection for each call and when image data downloading completed it will return image for imageview. and if image is already in cache then just return image without creating connection.
You can download "AsyncImageView" class files from following link:- https://www.dropbox.com/s/peazwjvky9fsjd7/Archive.zip
in .m file import AsyncImageView Class
#import "AsyncImageView.h"
in your tableview cell at indexpath method
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"SimpleTableCell";
UITableViewCell *cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(X, y, width, height)];
NSString *imageURL = [NSString stringWithFormat: #"www.xyz.image.png"];
AsyncImageView *async = [[AsyncImageView alloc]initWithFrame:CGRectMake(0, 0, width, height)];
[async loadImageFromURL:[NSURL URLWithString:imageURL]];
[imageView addSubview:async];
[cell addSubview:imageView];
return cell;
}
try this your problem will solve.

Related

Store image in cache Objective c

In UITableviewcell I have many Image view which will load the image from url
I want to store the image in cache so that when I scroll again it should not load from url
My Code is
UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[indicator startAnimating];
[indicator setCenter:cell.image_VW.center];
[cell.contentView addSubview:indicator];
NSString *url_STR = [NSString stringWithFormat:#"%#news/%#",IMAGE_URL,[tmp_NEWS valueForKey:#"image"]];
NSString *url_STR = [NSString stringWithFormat:#"%#news/%#",IMAGE_URL,[tmp_NEWS valueForKey:#"image"]];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//retrive image on global queue
UIImage * img = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:url_STR]]];
dispatch_async(dispatch_get_main_queue(), ^{
[indicator removeFromSuperview];
NEWS_BIG_CELL * cell = (NEWS_BIG_CELL *)[self.tbl_CONTENTS cellForRowAtIndexPath:indexPath];
//assign cell image on main thread
//cell.image_VW.contentMode = UIViewContentModeScaleAspectFit;
cell.image_VW.image = img;
});
});
you should use NSCache, where you can save you images by keys and get them if they are exist in cache (by key), please see this swift example :
http://www.splinter.com.au/2015/09/24/swift-image-cache/
or use:
https://github.com/rs/SDWebImage
For Simple solution is SDWebImage for caching image gitHub link
in your UITableviewcell cellForRowAtIndexPath method
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"MyIdentifier"];
[cell.image_VW sd_setImageWithURL:[NSURL URLWithString:#"http://www.domain.com/path/to/image.jpg"]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
return cell;
}
First you need to check if the data is not nil from dataWithContentsOfURL, then if the check is true:
you can save the image as NSData in NSUserdefaults with its URL as the key (setObject:(NSData) forKey:(URL as NSString)) of that preference, so that next time, you can check in the cache if any data exists for the URL to display it immediately without getting it again from the internet (objectForKey:(URL as NSString)).

Scrolling breaks in uitableview

I am having a problem viewing my tableview when i get the data of my cells from a server. If i do not use photos there is no breaks in the scrolling, but i want to use the images also. Can anyone knows how can i resolve this? I am getting the data from a plist in my server.
Here is the code of the image that makes the scrolling breaks (i am using custom style cell)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
NSURL *URL = [[NSURL alloc] initWithString:[[self.content objectAtIndex:indexPath.row] valueForKey:#"imageName"]];
NSData *URLData = [[NSData alloc] initWithContentsOfURL:URL];
UIImage *img = [[UIImage alloc] initWithData:URLData];
UIImageView *imgView = (UIImageView *)[cell viewWithTag:100];
imgView.image = img;
....
If you mean that the scrolling stops and starts, this might be because if the images are loaded from a server (which might take a noticeable amount of time to do), executing on the main thread causes freezing.
If this is the case, the fix is to fetch the images in another thread. Fortunately iOS has a fairly easy to use multithreading system called Grand Central Dispatch.
Here's some example code:
dispatch_queue_t q = dispatch_queue_create("FetchImage", NULL);
dispatch_async(q, ^{
/* Fetch the image from the server... */
dispatch_async(dispatch_get_main_queue(), ^{
/* This is the main thread again, where we set the tableView's image to
be what we just fetched. */
});
});

GCD jumbles the data on scrolling tableview

I'm using grand central dispatcher to load images from server but when i scroll the table the data, i.e. images, jumbles - means 1st image comes to some other place and like wise other images do.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"ItemImageCellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] ;
cell.selectionStyle=UITableViewCellSelectionStyleNone;
}
NSDictionary *item=[responseDictionary objectAtIndex:[indexPath row]];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);
NSString *actionForUser=[item objectForKey:#"action"];
objc_setAssociatedObject(cell,
kIndexPathAssociationKey,
indexPath,
OBJC_ASSOCIATION_RETAIN);
dispatch_async(queue, ^{
if([actionForUser isEqualToString:like])
{
NSURL *url = [NSURL URLWithString:[item objectForKey:#"user_image"]];
NSData *data1 = [[NSData alloc] initWithContentsOfURL:url];
UIImage *image1 = [[UIImage alloc] initWithData:data1];
//userProfileimage
UIButton *userImageButton = [[UIButton alloc] initWithFrame:CGRectMake(10,5, 40,40)];
userImageButton.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
userImageButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;
[userImageButton setBackgroundImage:image1 forState:UIControlStateNormal];
[userImageButton addTarget:self
action:#selector(userImageButtonclick:)
forControlEvents:UIControlEventTouchDown];
[cell.contentView addSubview:userImageButton];
}
});
return cell;
}
This is because by the time your async method has finished, cell has been recycled and used for a different index path, so you're updating the wrong cell.
At the point of update, get the cell reference by using the tableview's (not the data source method) cellForRowAtIndexPath: method. This will return the correct cell, or nil if the cell isn't on the screen any more. You can update this cell safely.
You should probably be adding the image data to your model as well so you aren't downloading it repeatedly.
As an example, instead of this line:
[cell.contentView addSubview:userImageButton];
You should have something like this:
UITableViewCell *cellToUpdate = [tableView cellForRowAtIndexPath:indexPath];
[cellToUpdate.contentView addSubview:userImageButton];
There are further problems with your code; you are not caching the images, you will be adding this subview every time this cell comes on screen, and if the cell is reused for a case where it doesn't need the button, the button will still be present. I have only addressed the "GCD jumbling" as described in your question.

Asynchronous image loading in UITableViewCell memory issue

In my iPhone application I have a tableview with custom imageview and loading images from remote location using AsyncImageView class. It works nicely, but one issue is, if I scroll the table, cells will be dequeued and it again trying to get the images from server. So, the method for loading image from AsyncImageView class is calling again and again hence increases the memory allocation, and eventually the app crashes.
Here is my code:
- (UITableViewCell *) getCellContentView:(NSString *)cellIdentifier {
CGRect CellFrame = CGRectMake(0, 0, 300, 40);
CGRect userImageFrame = CGRectMake(5, 7, 36, 36);
UIImageView *userImage;
UITableViewCell *cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier] autorelease];
[cell setFrame:CellFrame];
userImage = [[UIImageView alloc]init];
userImage.frame = userImageFrame;
userImage.tag = 3;
[cell.contentView addSubview:userImage];
[userImage release];
return cell;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil)
cell = [self getCellContentView:CellIdentifier];
else
[[AsyncImageLoader sharedLoader] cancelLoadingImagesForTarget:cell.imageView];
UIImageView *userImage = (UIImageView *)[cell viewWithTag:3];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.selectionStyle = UITableViewCellSelectionStyleGray;
NSString *url = [[NSString alloc]initWithFormat:#"%#%#", CommonImageURL,[AllUsersProfileImageArray objectAtIndex:indexPath.row]];
NSURL *imageUrl = [NSURL URLWithString:[url stringByAppendingFormat:#"?%i", rand()]];
[url release];
userImage.image = [UIImage imageNamed:#"defaultPerson.png"];
userImage.imageURL = imageUrl;
return cell;
}
Is there any possible way to fix the issue? Please help.
The best solution will be caching the image that is already downloaded and displaying it from there.
You need to write code for that, or there are some libraries which provide such feature:
HJCache
SDWebImage
The popular AFNetworking library also includes a UIImageView category to load images from the web which is often overlooked. I found it to be quite efficient with respect to memory usage and easy to use.
http://afnetworking.github.com/AFNetworking/Categories/UIImageView+AFNetworking.html
I came across same trouble of memory leaks loading multiple images from server. My application needed fresh images response every time (functionality factor)
I was using NSURLConnection using asynch requests. I tried with
NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:0 diskCapacity:0 diskPath:0];
[NSURLCache setSharedURLCache:sharedCache]; // in DidFinishLoading & didFailWithError
receivedData=nil; // at didReceiveResponse
receivedData=[[NSMutableData alloc] init];
but nothing really helped , until I removed my cell
[cell removeFromSuperview]; in cellForRowAtIndexPath and initialized it again FOR every new NSURL request (additional if condition on cellForRowAtIndexPath but REALLY that payed off).
Probably the UIImageViews were never removed and new images n data were added constantly as fetched by responses. Removing old UITableViewCell for a new NSURLRequest worked in my case. Hope this helps someone like me lost in cleaning NSURLCache and still memory beefing up.
I posted a custom solution here
Download image asynchronously .
I think it works ok and requires very little code.

How to load images asynchronously in uitableview without memory warning?

Hi iam developing an app where i will fetch images from web service and load the images in tableview. I loaded the images asynchronously. The problem is my app get crash while scrolling the tableview and in log it shows memory recieved warning.Also same images gets repeated in many rows.Also it takes more time to load. i used the below code.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
/* UILabel * cellLabel=[[UILabel alloc] initWithFrame:CGRectMake(0, 50, cell.frame.size.width-20, 45)];
cellLabel.textColor=[UIColor blackColor];
cellLabel.backgroundColor=[UIColor clearColor];
cellLabel.tag=2;
[cell.contentView addSubview:cellLabel];*/
cell.selectionStyle=UITableViewCellSelectionStyleNone;
cell.backgroundView=[[UIImageView alloc] initWithImage:[UIImage imageNamed:#"list_iPhone.png"]];
UIImageView *imv = [[UIImageView alloc]initWithFrame:CGRectMake(10,18, 48, 48)];
imv.tag=4;
imv.image=[UIImage imageNamed:#"ImagePlaceholder.png"];
[cell.contentView addSubview:imv];
UIImageView *arrwimv = [[UIImageView alloc]initWithFrame:CGRectMake(260,35, 14, 17)];
arrwimv.image=[UIImage imageNamed:#"arrw_iPhone.png"];
[cell.contentView addSubview:arrwimv];
UILabel *descriptionLbl=[[UILabel alloc] initWithFrame:CGRectMake(100, 27, 450, 45)];
descriptionLbl.font=[UIFont CITY311_TitleFontWithSize:18];
descriptionLbl.tag=1;
descriptionLbl.textAlignment=UITextAlignmentLeft;
descriptionLbl.textColor=[UIColor blackColor];
descriptionLbl.backgroundColor=[UIColor clearColor];
[cell.contentView addSubview:descriptionLbl];
UILabel *descriptionLbl2=[[UILabel alloc] initWithFrame:CGRectMake(100, 5, 450, 45)];
descriptionLbl2.font=[UIFont CITY311_TitleFontWithSize:18];
descriptionLbl2.tag=2;
descriptionLbl2.textAlignment=UITextAlignmentLeft;
descriptionLbl2.textColor=[UIColor blackColor];
descriptionLbl2.backgroundColor=[UIColor clearColor];
[cell.contentView addSubview:descriptionLbl2];
}
UIImageView *imv2=(UIImageView *)[cell.contentView viewWithTag:4];
dispatch_async(mainQueue, ^(void) {
if(![[[issueArray objectAtIndex:indexPath.row]objectForKey:#"PhotoUrl"] isEqualToString:#""])
{
NSData *imageData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:[[issueArray objectAtIndex:indexPath.row]objectForKey:#"PhotoUrl"]]];
UIImage* image = [[UIImage alloc] initWithData:imageData];
imv2.image = image;
}
});
UILabel *lbl=(UILabel *)[cell.contentView viewWithTag:1];
lbl.text=[[issueArray objectAtIndex:indexPath.row] objectForKey:#"issueSubmittedDate"];
UILabel *lbl2=(UILabel *)[cell.contentView viewWithTag:2];
lbl2.text=[[issueArray objectAtIndex:indexPath.row] objectForKey:#"IssueName"];
return cell;
}
In view did load
mainQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
In .h file
dispatch_queue_t mainQueue;
Please help to load the images properly without any memory warning(Crash).Thanks in Advance.
you are reusing tableviewcells. When a cell moves off the screen, it will be set aside so that you can reuse the object. When you are doing a dequeueReusableCellWithIdentifier you can get a 'old' cell that already contains an image. If you dont' clear the data from that cell you will see the old data (image) until the new image is downloaded.
inside the if(cell==nil) you should only create the cell and set properties that will be the same for every row. Set and clear the data below that if.
The crashes probably happen because a cell can be moved out of the view and reused for an other row before the initial callback is ready. Try setting the identifier to a unique value. Something like:
NSString *CellIdentifier = [NSString stringWithFormat:#"Cell%d", indexPath.Row];
Only keep your code like that when the number of rows is low. Otherwise you could get memory problems.
Instead of trying to fix it yourself, you could try using something like https://github.com/rs/SDWebImage

Resources