Lazy loading of Twitter profile images not working

Go To StackoverFlow.com

0

I am trying to create a simple Twitter client with lazy loading of Twitter images. I can get and display tweets in the UITableView but I am unable to load the image.

This is what I get, text and username loads correctly but no images:

enter image description here

I think the problem might be in this method which is called by the delegate once image loads and should display the image in table view cell:

// Called by ImageDownloader when image is ready to be displayed
- (void)imageDidLoad:(NSString *)downloadedImageUrl downloadedImage:(UIImage *)downloadedImage
{
    for (Tweet *tweet in self.tweets) {
        if (tweet.userProfileImageUrl == downloadedImageUrl) {
            tweet.userProfileImage = downloadedImage;
            UITableViewCell *tweetCell = [self.tableView cellForRowAtIndexPath:tweet.uiTableViewCellIndexPath];
            tweetCell.imageView.image = downloadedImage;
        }
    }
}

But I am pasting more of my code bellow just to be sure...


I have defined a simple class for my tweets:

@interface Tweet : NSObject
{
    NSString *userName;
    NSString *text;
    NSString *userProfileImageUrl;
    UIImage *userProfileImage;
    NSIndexPath *uiTableViewCellIndexPath;
}

@property (nonatomic, retain) NSString *userName;
@property (nonatomic, retain) NSString *text;
@property (nonatomic, retain) NSString *userProfileImageUrl;
@property (nonatomic, retain) UIImage *userProfileImage;
@property (nonatomic, retain) NSIndexPath *uiTableViewCellIndexPath;
@end

I have also implemented a simple image downloader class:

@protocol ImageDownloaderDelegate

- (void)imageDidLoad:(NSString *)downloadedImageUrl downloadedImage:(UIImage *)downloadedImage;

@end

@interface ImageDownloader : NSObject
{
    NSString *imageUrl;
    UIImage *downloadedImage;
    id <ImageDownloaderDelegate> __unsafe_unretained delegate;
    NSMutableData *activeDownload;
    NSURLConnection *imageConnection;
}

@property (nonatomic, retain) NSString *imageUrl;
@property (nonatomic, retain) UIImage *downloadedImage;
@property (unsafe_unretained) id <ImageDownloaderDelegate> delegate;
@property (nonatomic, retain) NSMutableData *activeDownload;
@property (nonatomic, retain) NSURLConnection *imageConnection;

- (void)startDownload;
- (void)cancelDownload;

@end

With this implementation:

#import "ImageDownloader.h"

#define kImageWidth 48
#define kImageHeight 48

@implementation ImageDownloader

@synthesize imageUrl;
@synthesize downloadedImage;
@synthesize delegate;
@synthesize activeDownload;
@synthesize imageConnection;

#pragma mark

- (void)startDownload
{
    self.activeDownload = [NSMutableData data];
    self.imageConnection = [[NSURLConnection alloc] initWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:self.imageUrl]] delegate:self];
}

- (void)cancelDownload
{
    [self.imageConnection cancel];
    self.imageConnection = nil;
    self.activeDownload = nil;
}

#pragma mark -
#pragma mark Download support (NSURLConnectionDelegate)

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
    // Clear the activeDownload property to allow later attempts
    self.activeDownload = nil;

    // Release the connection now that it's finished
    self.imageConnection = nil;
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    // Set userProfileImage and clear temporary data/image
    UIImage *image = [[UIImage alloc] initWithData:self.activeDownload];
    if (image.size.width != kImageWidth || image.size.height != kImageHeight) {
        CGSize itemSize = CGSizeMake(kImageWidth, kImageHeight);
        UIGraphicsBeginImageContext(itemSize);
        CGRect imageRect = CGRectMake(0.0, 0.0, itemSize.width, itemSize.height);
        [image drawInRect:imageRect];
        self.downloadedImage = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
    } else {
        self.downloadedImage = image;
    }

    self.activeDownload = nil;

    // Release the connection now that it's finished
    self.imageConnection = nil;

    // Call our delegate and tell it that our image is ready for display
    [delegate imageDidLoad:self.imageUrl downloadedImage:self.downloadedImage];
}

@end

In my master controller I load the tweets:

// Fetch the Twitter timeline and assigns tweets to the tweets property
- (void)fetchTweets
{
    self.tweets = [[NSMutableArray alloc] init];

    NSString *userTimelineUrl = @"https://api.twitter.com/1/statuses/user_timeline.json?include_entities=true&include_rts=true&screen_name=richardknop&count=25";

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:userTimelineUrl]];

        NSError *error;

        NSArray *tweetJsonObjects = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
        for (NSUInteger i = 0; i < [tweetJsonObjects count]; i++) {
            Tweet *tweet = [[Tweet alloc] init];
            tweet.userName = [[[tweetJsonObjects objectAtIndex:i] objectForKey:@"user"] objectForKey:@"name"];
            NSString * textString = [[tweetJsonObjects objectAtIndex:i] objectForKey:@"text"];
            tweet.text = textString;
            tweet.userProfileImageUrl = [[[tweetJsonObjects objectAtIndex:i] objectForKey:@"user"] objectForKey:@"profile_image_url"];
            [self.tweets addObject:tweet];
        }

        dispatch_async(dispatch_get_main_queue(), ^{
            [self.tableView reloadData];
        });

    });
}

This is how I load contents of the table view:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    int count  = [self.tweets count];
    return count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *cellIdentifier = @"TweetCell";

    UITableViewCell * tweetCell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
    if (tweetCell == nil) {
        tweetCell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
    }

    if ([self.tweets count] > 0) {

        Tweet *tweet = [self.tweets objectAtIndex:indexPath.row];
        tweet.uiTableViewCellIndexPath = indexPath;

        tweetCell.textLabel.text = tweet.text;
        tweetCell.detailTextLabel.text = tweet.userName;

        // Only load cached images; defer new downloads until scrolling ends
        if (!tweet.userProfileImage) {
            if (self.tableView.dragging == NO && self.tableView.decelerating == NO) {
                [self startImageDownload:tweet.userProfileImageUrl];
            }
            // If a download is deferred or in progress, return a placeholder image
            tweetCell.imageView.image = [UIImage imageNamed:@"Placeholder.png"];
        } else {
            tweetCell.imageView.image = tweet.userProfileImage;
        }

    }

    return tweetCell;
}

This method starts a download if a tweet image:

- (void)startImageDownload:(NSString *)userProfileImageUrl
{
    BOOL downloadAlreadyInProgress = NO;
    for (ImageDownloader *storedImageDownloader in self.imageDownloadsInProgress) {
        if (storedImageDownloader.imageUrl == userProfileImageUrl) {
            downloadAlreadyInProgress = YES;
        }
    }

    if (downloadAlreadyInProgress == NO) {
        ImageDownloader *imageDownloader = [[ImageDownloader alloc] init];
        imageDownloader.imageUrl = userProfileImageUrl;
        imageDownloader.delegate = self;
        [self.imageDownloadsInProgress addObject:imageDownloader];
        [imageDownloader startDownload];
    }
}
2012-04-03 20:01
by Richard Knop


1

Either you removed the important parts of the code because it was too long to post or you are missing the most important method from the NSURLConnectionDataDelegate (formerly known as NSURLConnectionDelegate) protocol. The one where you fill self.activeDownload with actual data.

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    [self.activeDownload appendData:data];
}
2012-04-03 20:29
by Matthias Bauch
That's not anywhere in this example: http://developer.apple.com/library/ios/#samplecode/LazyTableImages/Introduction/Intro.html#//appleref/doc/uid/DTS40009394-Intro-DontLinkElementID - Richard Knop 2012-04-03 20:48
I added that to ImageDownloader.m - and it worked :D Thanks, you are really smart - Richard Knop 2012-04-03 20:50
It actually is part of the example IconDownloader.m - Line 104. But great that it helped you. :- - Matthias Bauch 2012-04-03 21:11