1

I've created a category for UIImageView to allow for async loading of images:

@implementation UIImageView (ImageLoader)

- (void) asyncLoadImageIntoView:(NSURL *)imageURL withLoadingIndicator:(BOOL)isLoadingIndicator {

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:imageURL cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:60];

    (void)[[NSURLConnection alloc] initWithRequest:request delegate:self];
}

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response 
{
   // responseData = [[NSMutableData alloc] init];
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data 
{
    NSLog(@"didReceiveData %@", data);
    (void)[self.image initWithData:data];
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error 
{

//[textView setString:@"Unable to fetch data"];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection 
{
    NSLog(@"Succeeded! Received image data");
}



@end

My issue is that even though the connection:didReceiveData: method is called and it prints the data to the console, the [self.image initWithData:data] does not seem to be loading the image into the view. What is the problem here?

jscs
  • 63,694
  • 13
  • 151
  • 195
Wasim
  • 4,953
  • 10
  • 52
  • 87

4 Answers4

5

First, mehdzor's right; connection:didRecieveData: doesn't mean that you've got all the data; you need to wait until you get connectionDidFinishLoading: to be sure. If the image is very large, for example, it may not all come at once.

You should create an NSMutableData instance when you open the connection, and add to it in connection:didRecieveData::

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data 
{
    NSLog(@"didReceiveData %@", data);
    [[self mutableData] appendData:data]
}

Since this is a category, you will probably have to use the associated objects functions to store and access that mutable data instance.

Second, [self.image initWithData:data] doesn't make any sense. You can't initialize an object until you've allocated it, and you can't send init... to an object that's already been initialized.

The better way, though, since you're going to assign this image to a property, is to use UIImage's convenience method imageWithData:. Again, you need to do this in connectionDidFinishLoading:, and you will pass your mutable data instance, which now has all the data that the connection managed to pull down:

- (void)connectionDidFinishLoading:(NSURLConnection *)connection 
{
    NSLog(@"Succeeded! Received image data");
    [self setImage:[UIImage imageWithData:[self mutableData]]];
}

Don't forget to clean up (release) the mutableData instance after this.

Community
  • 1
  • 1
jscs
  • 63,694
  • 13
  • 151
  • 195
4

There are at least two mistakes in your code.

The first mistake is that you should not be calling [self.image initWithData] as all init methods are meant to be called once, and during construction of an object, right after the alloc:

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data 
{
    NSLog(@"didReceiveData %@", data);
    (void)[self.image initWithData:data]; // Wrong...
}

The second mistake is that the method above is incremental, and can be called multiple times before the full image is received. What you want to do instead is store your data in a NSMutableData object (or similar) until all data has been received, and create an image once you get the message:

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
   // Create an image with your NSMutableData object here
}

You will probably need to use something other than a category to do this properly, as you'd probably need new properties.

diegoreymendez
  • 1,997
  • 18
  • 20
0

I think your problem is that you create image every time you receive part of it. You should gather all data in one piece and than create an image;

Mehdzor
  • 789
  • 4
  • 9
  • 23
  • I'm not sure about that because the NSLog only gets called once, so technically the data object should be the full image. – Wasim May 27 '12 at 22:11
0

I have fixed the issue. I'm not exactly sure why, but creating a UIImage from the data first, then assigning the image of the UIImageView to the UIImage worked.

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data 
{
    UIImage *img = [[UIImage alloc] initWithData:data];
    self.image = img;
}
Wasim
  • 4,953
  • 10
  • 52
  • 87
  • 1
    You were leaking memory there - I edited your answer. You need to release img before the end of your method. – diegoreymendez May 27 '12 at 22:27
  • 1
    Thanks, i'm using ARC so thankfully don't need to worry about releasing anymore, but I understand it's important for those who are not using it yet. THe problem with my answer is that it doesn't load JPEGS fully so I've had to change my category to a subclass in order to store the data in `didReceiveData` then display it in `connectionDidFinishLoading` – Wasim May 28 '12 at 07:59
  • @Wasim: You don't need to subclass; see the link I provided in my answer about associated objects. – jscs May 28 '12 at 08:45
  • Sorry then, time to review ARC myself. :) – diegoreymendez May 28 '12 at 12:12