0

I am using "UIImageView+AFNetworking.h", and my goal is to take a NSArray of URL called imageURLArray, download all the pictures, put them in a scroll view with page controls.

To do this, I am downloading the pictures one at a time in the following loop, and have a previously allocated/initialized mutable array downloadImageArray where I stored the downloaded picture:

for (int i = 0; i < [imageURLArray count]; i++){
        NSURL *testURL = [imageURLArray objectAtIndex:i];
        NSURLRequest *testURLRequest = [[NSURLRequest alloc] initWithURL:testURL];
        [placeHolderImageView setImageWithURLRequest:testURLRequest placeholderImage:nil success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
            NSLog(@"success");
            NSLog(@"The image is %@", image);
            [downloadedImageArray addObject:image]; //! the exception is thrown here - it says the object is NULL
        } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
            NSLog(@"failed to download");
        }];
    }
NSLog(@"Count of images is %i", [downloadedImageArray count]) //this always returns "0"

As comment in the line where I add the object to downloadedImageArray, I get the exception that the object added to the mutable array is NULL. I suspect that this is because the image hasn't completed downloading before the next URL is put in place and is being asked to download again. Also, after the loop, when I get a count of downloadedImageArray, the count returns 0.

My questions are:

  1. How would be able to ensure that one image has completed downloading before looping through to the next request to download the next image?
  2. As a design choice, does it make sense to download all the images first before adding them to my scroll view (the width of each image is set fixed), or does it make more sense to download as they are being scrolled to? If so, how could I implement this? Most of the time imageURLArray has no more than 4 set of URLs.

Thanks!

daspianist
  • 5,336
  • 8
  • 50
  • 94
  • Why are you trying to assign every image to the same image view? – rmaddy Nov 18 '13 at 17:30
  • I used the `placeHolderImageView` because I need an imageView to then call `setImageWithUrlRequest` - at least this is my understanding. – daspianist Nov 18 '13 at 17:34
  • If your goal is to download images just to store them in an array, why are you using an image view at all? Just download the images using AFNetworking. It has more direct APIs for downloading files from remote URLs. – rmaddy Nov 18 '13 at 17:36
  • Thanks for the suggestion, and my goal indeed is to just download the `UIImages` (and I am currently using AFNetworking). However, I don't know of any methods in `AFNetworking` where its doesn't require an UIImageView to work since all of them are variations of `[UIImageView setImageWithURL NSURL]`. Could you point out to which method you were referring to in the API? – daspianist Nov 18 '13 at 17:39
  • possible duplicate of [Download a file / image with AFNetworking in iOS?](http://stackoverflow.com/questions/11186854/download-a-file-image-with-afnetworking-in-ios) – rmaddy Nov 18 '13 at 17:43

3 Answers3

3

If you're using the latest version of AFNetworking, to download images you can do this:

NSURLRequest* request = [...]
AFHTTPRequestOperation* op = [[AFHTTPRequestOperation alloc] initWithRequest:request];
op.responseSerializer = [AFImageResponseSerializer serializer];
[op setCompletionBlockWithSuccess:^(AFHTTPRequestOperation* operation, id responseObject){...} failure:^(AFHTTPRequestOperation* operation, NSError* error) {...}];

[op start];

So if you want to loop through an array of URLs and download an image from each, you'd do this (and a word of warning: I haven't tested this):

NSMutableArray* images = [NSMutableArray arrayWithCapacity:imageURLArray.count];
for(int i = 0; i < imageURLArray.count; i++)
    [images addObject:[NSNull null]];
for(int i = 0; i < imageURLArray.count; i++)
{
    NSURL* url = imageURLArray[i];
    NSURLRequest* request = [NSURLRequest requestWithURL:url];
    AFHTTPRequestOperation* op = [[AFHTTPRequestOperation alloc] initWithRequest:request];
    op.responseSerializer = [AFImageResponseSerializer serializer];
    [op setCompletionBlockWithSuccess:^(AFHTTPRequestOperation* operation, id responseObject){
        UIImage* image = responseObject;
        [images replaceObjectAtIndex:i withObject:image];
    } failure:^(AFHTTPRequestOperation* operation, NSError* error) {...}];

    [op start];
}

You fill the images array with NSNulls because images at different indices may load at different times, and if, say image at index 3 loads before image at index 2, you'll get an out of bounds exception if you try to insert it at the proper index.

When these operations finish, each image in the images array will correspond to the URL at the same index in the imageURLArray.

Having said that, most of the time it's better to just download the images you need, when you need them. So I would make your view controller a delegate of your image scroll view, then when scrolling occurs, figure out which UIImageViews have appeared, and call setImageWithURL on them. However, if that's too hard and you don't have an obscene number of really large images, the above should be fine.

Shinigami
  • 2,123
  • 23
  • 40
  • Thank you - this did it! Adding null images to populate the array was definitely what I needed to do. And thanks for the suggestion about downloading just the images I need. I made refactor to take this into consideration. – daspianist Nov 18 '13 at 19:29
1

Ans. 1 We can make waiting for process completion using NSRunLoop Try this

for (int i = 0; i < [imageURLArray count]; i++)
{
NSURL *testURL = [imageURLArray objectAtIndex:i];
NSURLRequest *testURLRequest = [[NSURLRequest alloc] initWithURL:testURL];
[placeHolderImageView setImageWithURLRequest:testURLRequest placeholderImage:nil success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
    NSLog(@"success");
    NSLog(@"The image is %@", image);

    while (!image)
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];

    [downloadedImageArray addObject:image]; //! the exception is thrown here - it says the object is NULL
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
    NSLog(@"failed to download");
}];
}
NSLog(@"Count of images is %i", [downloadedImageArray count]);

Now images is not added to array until its not downloaded.

Ans. 2 follow this process for showing images

a. download image

b. add to array

c. add to scroller

do this until all images are not downloaded.

Anand Suthar
  • 3,678
  • 2
  • 30
  • 52
  • Read the comments under the question. There is no need to be using an image view and loading images into it. – rmaddy Nov 18 '13 at 18:15
  • I am just modify it's code to prevent array to add nil images. Which is daspianist first problem and now it's up to him how he use it's modified code. – Anand Suthar Nov 18 '13 at 18:20
  • You should known that using run loop within a completion handler to "wait" for the asynchronous method to complete is - at least - futile. And for other reasons, using a run loop here won't work at all. – CouchDeveloper Nov 18 '13 at 18:26
0
  1. if you haven't declared downloadedImageArray with __block then it won't be written to from within your placeHolderImageView setImageWithURLRequest block;

  2. consider using table view instead of scroll view so that the downloads happen only when cells are being shown to the user and also consider downloading thumbnails first, whole images later;

SPA
  • 1,279
  • 8
  • 13
  • Your first point is incorrect. You can call methods on a non `__block` variable in a block. What you can't do is assign a new value to the variable. – rmaddy Nov 18 '13 at 18:03