loading asynchronous images in tableview, they disappear at every scroll - iphone

I'm parsing an xml file, containing urls for thumb images. I want to show those thumbs in an uitableview.
Using class AsyncImageView (found on this website), my code works.
The only problem is this: every time that the user scrolls the uitableview, the images disappear for a moment... then re-appear (i think that i'm downloading the image every time but i'm not sure)!
That's an ugly effect, i want that if the image is downloaded, it stay visible.
this is my code (NB: in array_thumb there are the urls parsed before):
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *CellIdentifier;
static NSString *NibNamed;
UIDeviceOrientation deviceOrientation = [UIApplication sharedApplication].statusBarOrientation;
if (deviceOrientation != UIDeviceOrientationLandscapeLeft &&
deviceOrientation != UIDeviceOrientationLandscapeRight)
{
CellIdentifier= #"Cell1";
NibNamed = #"cell_gallery";
}
else
{
CellIdentifier= #"Cell2";
NibNamed = #"cell_gallery_landscape";
}
[[NSBundle mainBundle] loadNibNamed:NibNamed owner:self options:NULL];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:NibNamed owner:self options:nil];
cell = [nib objectAtIndex:0];
}
else {
AsyncImageView* oldImage = (AsyncImageView*)[cell.contentView viewWithTag:1];
[oldImage removeFromSuperview];
}
CGRect frame;
frame.size.width=72; frame.size.height=72;
frame.origin.x=10; frame.origin.y=10;
AsyncImageView* asyncImage = [[[AsyncImageView alloc] initWithFrame:frame] autorelease];
NSString *title = [NSString stringWithFormat:#"%#", [array_title objectAtIndex:indexPath.row]];
UILabel *testoLabel = (UILabel*)[cell viewWithTag:2];
testoLabel.text = title;
asyncImage.tag = 1;
NSLog(#"%#", asyncImage);
NSURL *url = [NSURL URLWithString:[array_thumb objectAtIndex:indexPath.row]];
asyncImage.layer.cornerRadius = 10.0;
asyncImage.layer.masksToBounds = YES;
[asyncImage loadImageFromURL:url];
[cell.contentView addSubview:asyncImage];
UIColor *miocolore1 = [[UIColor alloc] initWithRed:247.0 / 255 green:247.0 / 255 blue:247.0 / 255 alpha:1.0];
UIColor *miocolore2 = [[UIColor alloc] initWithRed:233.0 / 255 green:233.0 / 255 blue:233.0 / 255 alpha:1.0];
if (indexPath.row % 2 == 0) {
cell.contentView.backgroundColor = miocolore1;
}
else {
cell.contentView.backgroundColor = miocolore2;
}
return cell;
}

Nothing in your code or in AsyncImageView suggest that the images are cached so it seems the same image gets downloaded repeatedly.
AsyncImageView is using the following to load the image:
[NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy];
You probably want to make sure that caching is actually happening here by altering the cache policy.
I'd checkout ASIHTTPRequest which has a great cache implementation.

As the cell are reused, you have to provide the data yourself. Either you can save the downloaded image as NSData or UIImage and provide the url to download or you can use the already downloaded data.
This way it wont download the image everytime you scroll.

Related

JSON Image Parsing/Prefixed URL

I have established successful parsing of text and images in a table view using JSON/PHP/MYSQL. I am only storing the location of the images in the database and the actual images are stored in a directory on my server. The only thing that is stored related to the image in the database is the name. Example car.jpg. What I want to do is prefix the URL of the image location on my server so they can be parsed without me having to go into the DB and manually entering the URL. Here is some of my code...
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *identifier = #"studentsCell";
StudentsCell *cell = (StudentsCell *)[tableView dequeueReusableCellWithIdentifier:identifier];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"StudentsCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
NSDictionary *studentsDict = [students objectAtIndex:indexPath.row];
//I want to prefix the URL for the key imagepath but i dont know where and how to do it.
NSURL *imageURL = [NSURL URLWithString:[studentsDict objectForKey:#"imagepath"]];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
UIImage *imageLoad = [[UIImage alloc] initWithData:imageData];
cell.imageView.image = imageLoad;
NSString *name = [NSString stringWithFormat:#"%# %#", [studentsDict valueForKey:#"first"], [studentsDict valueForKey:#"last"]];
cell.title.text = name;
NSString *subtitle = [NSString stringWithFormat:#"%#", [studentsDict objectForKey:#"email"]];
cell.subtitle.text = subtitle;
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = CGRectMake(265, 6, 44, 44);
[button setImage:[UIImage imageNamed:#"email.png"] forState:UIControlStateNormal];
[button addTarget:self action:#selector(email:) forControlEvents:UIControlEventTouchUpInside];
[cell.contentView addSubview:button];
// cell.backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"cellbackground.png"]];
return cell;
}
Let's assume you had something like:
NSURL *baseURL = [NSURL URLWithString:#"http://www.your.site.here/images"]; // whatever the folder with the images is
Then you could do:
NSURL *imageURL = [baseURL URLByAppendingPathComponent:[studentsDict objectForKey:#"imagepath"]];
By the way, you should consider using a UIImageView category, such as SDWebImage. Then, instead of loading a NSData with the image data synchronously, you can do an asynchronous image load:
[cell.imageView setImageWithURL:imageURL
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
The placeholder is what should be shown while the image is being loaded (perhaps just a blank image), and SDWebImage will then asynchronously retrieve the image and update the cell when it's retrieved. This will yield a far more responsive user interface. It will also avail yourself of image caching (so if you scroll down and then back up, the image won't be retrieved again).
AFNetworking has a similar UIImageView category, but not quite as robust of an implementation. But if you're already using AFNetworking, it's an option.

Fetching and displaying facebook profile picture in a UIImage

I'm trying to make a facebook friend picker displaying the friends who currently have the app installed that are also facebook friends with the current user.
Here I'm fetching the user's facebook profile picture but, I seem to have a warning where the last statement is not used. Thanks for your help.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"friendCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
PFUser *facebookID = [[PFUser alloc] init];
facebookID = [friendUsers objectAtIndex:0];
NSString *fbIdString = [[NSString alloc] init];
fbIdString = [facebookID objectForKey:#"fbId"];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
NSURL *profilePictureURL = [NSURL URLWithString:[NSString stringWithFormat:#"https://graph.facebook.com/%#/picture?type=large", fbIdString]];
NSData *picData = [NSData dataWithContentsOfURL:profilePictureURL];
UIImage *fbPic = (UIImage *)[self.view viewWithTag:1001];
[fbPic initWithData:picData];
}
// Configure the cell...
return cell;
}
You need to actually assign the result of the initWithData: call to your fbPic image.
E.g.
fbPic = [fbPic initWithData: picData];
From the apple documentation on UIImage:
- (id)initWithData:(NSData *)data
Parameters
data -
The data object containing the image data.
Return Value
An initialized UIImage object, or nil if the method could not initialize the image from the specified data.
A couple of things:
UIImage *fbPic = (UIImage *)[self.view viewWithTag:1001]; - I think you're trying to get the UIImageView from the cell?
There's no UIImageView where you're actually showing the image.
It should be something like this:
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
NSURL *profilePictureURL = [NSURL URLWithString:[NSString stringWithFormat:#"https://graph.facebook.com/%#/picture?type=large", fbIdString]];
NSData *picData = [NSData dataWithContentsOfURL:profilePictureURL];
UIImageView *fbPicImageView = (UIImageView *)[self.view viewWithTag:1001];
[fbPicImageView setImage:[UIImage imageWithData:picData]];
}

ASIHTTPRequest is not updating the DownloadProgress Delegate

I am using ASIHTTPRequest to download a file locally to the iDevice.
My download code is as follows
ASIHTTPRequest *download = [ASIHTTPRequest requestWithURL: videoUrl];
[download setCompletionBlock:^{
NSLog(#"Download Success");
// other code
}];
[download setFailedBlock:^{
NSLog(#"Download Failed");
// other code
}];
[download setDownloadProgressDelegate: [item progressDelegate]];
[download startAsynchronous];
NSLog(#"Start Download of %#", [item name]);
The object item holds a reference to a UIProgressView It is show on the screen but never updated.
In an attempt to debug, I subclassed UIProgressView and added the following log
- (void)setProgress:(float)newProgress {
NSLog(#"Current Progress : %f", newProgress);
[super setProgress: newProgress];
}
My Console now shows the progress going from 0.0 to 1.0 over ~50 iterations (nice!) but the uiProgressView doesnt change and at the end the NSLog show 0.5 the default setting for the progress view.
Anyone have an Idea what is happening?
EDIT
UIProgressView is accessed with this
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView cellForRowAtIndexPath: indexPath];
id progressView = [cell viewWithTag:VideoDetailProgressView];
[VideoDownloadManager queueDownloadOfVideo:video progressDelegate: progressView];
}
I have stepped through and watched it appears to keep the correct reference to the UIProgressView throughout
Edit
TableView Methods
// Row display. Implementers should *always* try to reuse cells by setting each cell's reuseIdentifier and querying for available reusable cells with dequeueReusableCellWithIdentifier:
// Cell gets various attributes set automatically based on table (separators) and data source (accessory views, editing controls)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *detailCellIdentifier = #"VideoDetailCell";
static NSString *categoryCellIdentifier = #"VideoCategoryCell";
UITableViewCell *cell = nil;
bool isCategorical = [[self.videoList objectAtIndex: indexPath.row] objectForKey:#"parentName"];
if(isCategorical)
{
cell = [tableView dequeueReusableCellWithIdentifier:categoryCellIdentifier];
}
else
{
cell = [tableView dequeueReusableCellWithIdentifier:detailCellIdentifier];
}
if (cell == nil && !isCategorical) {
[[NSBundle mainBundle] loadNibNamed:#"VideoDetailCell" owner:self options:nil];
cell = self.videoDetailCell;
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(5.0, 10.0, 46.0, 46.0)];
[cell addSubview:imageView];
imageView.hidden = !self.canEdit;
imageView.tag = VideoDetailsFavoriteButton;
[imageView release];
self.videoDetailCell = nil;
}
else if(cell == nil && isCategorical)
{
//Different cell
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:categoryCellIdentifier] autorelease];
}
[cell setBackgroundColor:[UIColor clearColor]];
return cell;
}
// Display customization
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
NSDictionary *object = [self.videoList objectAtIndex:indexPath.row];
bool isCategorical = [[self.videoList objectAtIndex: indexPath.row] objectForKey:#"parentName"];
if(isCategorical) {
cell.textLabel.textColor = [UIColor whiteColor];
cell.textLabel.text = [object objectForKey:#"name"];
NSUInteger videoCount = [[Videos sharedVideos] countById: [object objectForKey:#"name"]];
cell.detailTextLabel.textColor = [UIColor whiteColor];
cell.detailTextLabel.text = [NSString stringWithFormat: #"%d videos", videoCount];
}
else
{
[[cell viewWithTag:VideoDetailCellTitle] setValue:[object objectForKey:#"name"] forKey:#"text"];
[[cell viewWithTag:VideoDetailCellSubtitle] setValue:[object objectForKey:#"dateAdded"] forKey:#"text"];
[[cell viewWithTag:VideoDetailCellDuration] setValue:[object objectForKey:#"duration"] forKey:#"text"];
UIHTTPImageView *asyncImage = (UIHTTPImageView *)[cell viewWithTag:VideoDetailCellImage];
NSURL *thumbUrl = [NSURL URLWithString:[NSString stringWithFormat: #"%#%#", kRootUrlPath, [object objectForKey:#"image"]]];
[asyncImage setImageWithURL:thumbUrl placeholderImage: [UIImage imageNamed: kLoadingImage]];
asyncImage.clipsToBounds = YES;
UIImageView *editButton = (UIImageView *)[cell viewWithTag:VideoDetailsFavoriteButton];
if ([VideoDownloadManager isQueued: [object objectForKey: #"name"]]) {
[[cell viewWithTag:VideoDetailCellSubtitle] setHidden:YES];
[[cell viewWithTag:VideoDetailProgressView] setHidden:NO];
} else {
[[cell viewWithTag:VideoDetailCellSubtitle] setHidden:NO];
[[cell viewWithTag:VideoDetailProgressView] setHidden:YES];
}
if ([VideoDownloadManager isFavorites: [object objectForKey: #"name"]] || [VideoDownloadManager isQueued: [object objectForKey: #"name"]]) {
editButton.image = [UIImage imageNamed: kFavoritesHighlighted];
} else {
editButton.image = [UIImage imageNamed: kFavoritesEmpty];
}
}
}
Can you check if the update is made in the main thread? (it should be the case if you're using the latest version of ASIHTTPRequest)
NSLog(#"Current Progress : %f (main thread=%d)", newProgress, [NSThread isMainThread]);
If it doesn't help, can you show the code that is using the UIProgressView in the view?
Thanks for everyones help. You guys guided me to then answer. My cell was not correctly being assigned a reuseIdentifier
this was causing the whole issue.

Iphone loading an URL to a variable - static works, dynamic from XML falls over

I load my XML in to my iphone app via:
NSArray *Images = [doc nodesForXPath:kName_logo error:nil];//Root node
for (CXMLElement *ImgIcons in Images)
{
NSArray *locImage = [ImgIcons elementsForName:kName_image];
for(CXMLElement *fullImage in locImage)
{
LocationImage = fullImage.stringValue;
locStatImage = LocationImage;
NSLog(#"Image Found %#",LocationImage);
break;
}
}
Which is fine, it's bringing me back the correct string
2011-11-03 14:48:43.572 Del Search[13152:f203] Image Found http://de1128/directenquirieswebapplicationv3.0/images/logos/PremierInn.jpg
However, When I load it on to my table through the following:
if (indexPath.section == 0)
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifierImage];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifierImage] autorelease];
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0,0,320,180)];
imageView.tag = 1;
NSURL *Imageurl = [NSURL URLWithString:[NSString stringWithFormat:#"%#", locStatImage]];
NSData *data = [NSData dataWithContentsOfURL:Imageurl];
UIImage *img = [[[UIImage alloc] initWithData:data] autorelease];
imageView.image = img;
[cell addSubview:imageView];
[imageView release];
}
return cell;
}
The app falls over. However if I manually set locStatImage by doing:
locStatImage = #"http://de1128/directenquirieswebapplicationv3.0/images/logos/PremierInn.jpg";
It works fine. What' am I doing wrong when I set locStatImage?
Thanks
Tom
Assuming the two code snippets above are from separate methods, locStatImage is probably being autoreleased between you obtaining it from the XML and using it in your cellForRowAtIndexPath method.
Further assuming locStatImage is a retained property (if it isn't it should be), in your first code sample you should use
self.locStatImage = ...

UITableView Accessory View not being displayed

Why is the following not displaying the proper UITableView accessory view? It is simply displaying the UIAcessoryTypeCheckmark that was selected in the nib file.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForManagedObject:(NSManagedObject *)managedObject withIndexPath:(NSIndexPath *)indexPath{
static NSString *CellIdentifier = #"Selection";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil) {
cell = tvCell;
self.tvCell = nil;
}
self.tableView.rowHeight = 70.0f;
//Background
BOOL useDarkBackground = NO; //odd
if(indexPath.row %2 == 0) useDarkBackground = YES; //even
NSString *backgroundImagePath = [[NSBundle mainBundle] pathForResource:useDarkBackground ? #"DarkBackground" : #"LightBackground" ofType:#"png"];
UIImage *backgroundImage = [[UIImage imageWithContentsOfFile:backgroundImagePath] stretchableImageWithLeftCapWidth:0.0 topCapHeight:1.0];
cell.backgroundView = [[[UIImageView alloc] initWithImage:backgroundImage] autorelease];
cell.backgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
cell.backgroundView.frame = cell.bounds;
//Checkmark
cell.userInteractionEnabled = YES;
NSString *checkmarkImagePath;
checkmarkImagePath = [[NSBundle mainBundle] pathForResource:[selectedObjects containsObject:managedObject] ? #"checkSelected" : #"checkUnselected" ofType:#"png"];
if([selectedObjects count] == 0) {
checkmarkImagePath = [[NSBundle mainBundle] pathForResource:#"checkUnselected" ofType:#"png"];
}
UIImage *checkmarkImage = [[UIImage imageWithContentsOfFile:checkmarkImagePath] stretchableImageWithLeftCapWidth:0.0 topCapHeight:1.0];
UIImageView *imageView = [[[UIImageView alloc] initWithImage:checkmarkImage] autorelease];
imageView.contentMode = UIViewContentModeScaleAspectFit;
imageView.frame = CGRectMake(10.0, 10.0, imageView.frame.size.width/1.2, imageView.frame.size.height/1.2);
return cell;
}
For some reason, the following does work:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSManagedObject *objectToChange = [[self fetchedResultsControllerForTableView:tableView] objectAtIndexPath:indexPath]; //The object
[tableView deselectRowAtIndexPath:[tableView indexPathForSelectedRow] animated:NO];
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
NSString *checkmarkImagePath = [[NSBundle mainBundle] pathForResource:[selectedObjects containsObject:objectToChange] ? #"checkSelected" : #"checkUnselected" ofType:#"png"];
UIImage *checkmarkImage = [[UIImage imageWithContentsOfFile:checkmarkImagePath] stretchableImageWithLeftCapWidth:0.0 topCapHeight:1.0];
UIImageView *imageView = [[[UIImageView alloc] initWithImage:checkmarkImage] autorelease];
imageView.contentMode = UIViewContentModeScaleAspectFit;
imageView.frame = CGRectMake(10.0, 10.0, imageView.frame.size.width/1.2, imageView.frame.size.height/1.2);
cell.accessoryView = imageView;
}
Thanks a lot!
It doesn't look like you're adding the UIImageView to the accessory view of the cell in the first code block.
You're also leaking all over the place. Make sure you adhere to the memory management guidelines set forth by Apple.
In your cellForRowAtIndexPath, you are not assigning the accessoryView to the cell, as you are in the didSelect method.