1

Just to better give an idea of what this post is about, it ultimately ends out in this question:

How do I asynchronously download a not predefined number of images from any number of url's, add them to a dictionary (or array, if that's easier) so that they are added in the order the download is started, instead of adding them in the order they are finished?

This is the backbone question of this post, however for good measures and to actually allow one to understand what I mean, I have added my specific case involving this question in the following post. I know it's long, but it's pretty complex to explain my case.

So, here goes nothing:

I have a tableView which loads a couple of things from different arrays. All the arrays is created from a JSON fetch each time the app is launched, the idea being that I can update the info in my app by simply updating the JSON text file. One of the entries in the JSON text file contain url's to images, which I want to add to the tableView cell's contentview. I got the JSON fetch working, and a dictionary "dataDictionary" containing the info from the fetch is created.

The arrays then, get created like this:

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {

   // Array is created from the dictionary created from the JSON fetch

    _dataArray = _dataDictionary[@"games"];

   // Arrays that will eventually hold info from JSON file is created

    _Titles = [[NSMutableArray alloc] init];
    _Descriptions = [[NSMutableArray alloc] init];
    _Date = [[NSMutableArray alloc] init];

    //images will be added to dictionary instead
    _imageDict = [[NSMutableDictionary alloc]init];


   // Info parsed from the JSON fetch, is now added to arrays

for (NSDictionary *eachData in _dataArray)
    {
        _Title = eachData[@"Title"];
        _Description = eachData[@"Description"];
        _Date = eachData[@"Release"];

   // Key for image url is created
        _image = eachData[@"ImageLink"];


        [_Titles addObject:_Title];
        [_Descriptions addObject:_Description];
        [_Dates addObject:_Date];

Now there is more code below this, which is where I handle the images (will come just after this short explanation and below code sample), as you can see I have specified a key named "image" for the ImageLink entry in the JSON. Now I call this method, which starts an asynchronous download of the images:

- (void)downloadImageWithURL:(NSURL *)url completionBlock:(void (^)  (BOOL succeeded, UIImage *image))completionBlock
{
    NSMutableURLRequest *request = [NSMutableURLRequest    requestWithURL:url];
    [NSURLConnection sendAsynchronousRequest:request
                                   queue:[NSOperationQueue mainQueue]
                       completionHandler:^(NSURLResponse *response,  NSData *data, NSError *error) {
                               if ( !error )
                               {
                                   UIImage *image = [[UIImage alloc]  initWithData:data];
                                   completionBlock(YES,image);
                               } else{
                                   completionBlock(NO,nil);
                               }
                           }];


}

This method is called in the below code, so to continue the code from before:

// Just to be clear, we are now back in the "for (NSDictionary *eachData in _dataArray)" statement from the first code sample


//calling the above method here

[self downloadImageWithURL:[NSURL URLWithString:_image] completionBlock:^(BOOL succeeded, UIImage *image) {
            if (succeeded) {

//All this code runs when an image is successfully downloaded
NSLog(@"image download succesfull");

                UIImage *downloadedImage = [[UIImage alloc] init];

                // Doing this so I can resize the downloaded image to a proper size
                downloadedImage = image;

                //long boring code to resize image here


                UIImage *resizedImage = [[UIImage alloc] init];


                // resizedImage is as the name implies, the resized image that now has a proper size for the cell's content view



                 _number++; //Int that start out as -1, this increments each time an image is downloaded to allow the images to be added to the dictionary with the keys 0, 1, 2...

                // Wrapping the int in a NSNumber
                NSNumber *number = [NSNumber numberWithInt:_number];

                // Turning the NSnumber into a string
                NSString *key = [number stringValue];


                // Images finally being added to the dictionary, but, in the wrong "order" - or rather, with the wrong keys / numbers
                [_imageDict setObject:resizedImage forKey:key];




                //Update tableView when images are added to dictionary
                if (_imageDict.count == _gameTitles.count) {
                [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationFade];
                }


            }
        }];







      //Update tableView when data is added to array, this is to allow info to be shown even before the images are done downloading
        if (Titles.count == _dataArray.count) {


            // Update tableView with loaded content
            [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationFade];

            }
        } // "for" statement ends here
}

To sum the most important parts up:

When filling the cell's in the tableView, I use the indexPath.row to get the proper info from each array. The images download, and I add them to an dictionary with the keys 0, 1, 2.. when finished, however, because the download is asynchronous the download do not finish in the order it was initialized, rather, the smaller images get (not surprisingly) downloaded and added to the image dictionary first. This means that the keys of the images does not fit with the order in all my other arrays, so setting the image in the first cell with indexPath.row simply sets the image that was downloaded first, rather than the image download that was started first. Basically, even if image 5 gets downloaded first, it should be added with the key "4", and not "0".

I should also mention that because of the JSON fetch I do NOT know how many pictures will be downloaded beforehand as this can change depending on the JSON. This cancels out a lot of the answers here on stackoverflow (and other places as well).

So all this text and code and whatnot leads to the beginning question, how do I asynchronously download a not predefined number of images from any number of url's, add them to a dictionary (or array, if that's easier) so that they are added in the order the download is started, instead of adding them in the order they are finished?

Thank you very much for taking your time to read this.

4
  • Apple has provided sample code for Asynchronous Image download and to download in order they initialise you can recursive method so start first image download and when it's download finish then start second method's download. Link : developer.apple.com/library/ios/samplecode/LazyTableImages/… Commented May 3, 2015 at 13:54
  • I really like this approach, however I have been fiddling with this way too much by now, so I just really want it to work now.. The SDWebImage Library suggested by pandaren codemaster seems to be a lot easier to implement, especially considering the code I have already written, but I might use this approach in the future, that way I can implement it a lot easier instead of having to rethink my code ;) but thanks anyway! I wasn't aware that Apple had made sample codes like this. Commented May 4, 2015 at 17:19
  • @Chikara you can accept my answer for future reference to others if it is the solution for you. Commented May 4, 2015 at 21:52
  • @pandarencodemaster dammit I thought up until now that it was Malay Soni who wrote that comment! I accepted your answer, as I am very close to get it working as I want it to with your method/answer Commented May 7, 2015 at 17:59

1 Answer 1

1

So all this text and code and whatnot leads to the beginning question, how do I asynchronously download a not predefined number of images from any number of url's, add them to a dictionary (or array, if that's easier) so that they are added in the order the download is started, instead of adding them in the order they are finished?

You can use SDWebImage Library to download images,it asynchronously downloads your images from given URLs and it also caches images and manages all the things for you and it is really popular.All you have to do is to add below code to your -cellForRowAtIndexPath delegate method:

[cell.imageView sd_setImageWithURL:[NSURL URLWithString:imageURLThatYouGetFromJSONResponse]
                  placeholderImage:[UIImage imageNamed:@"placeholder.png"]];

Also I recommend you not to use arrays or dictionaries.You can store that all information in an object.It is easy and best-practise in many cases and called as object-oriented approach.You create an NSObject subclass and in there create properties "title", "description", "name", "imageURL".You can follow this OOP Tutorial for some better understanding.

You don't care about number of images, because you define the number of rows in -numberOfRowsInSection and -cellForRowAtIndexPath is called the times you wrote in -numberOfRowsInSection.You can add:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 
    return [arrayOfYourObjects count];
}

Edit: How to manipulate images while using SDWebImage?

Answer: You can use SDWebImageManager, it also manages caching for you

SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager downloadImageWithURL:imageURL
                      options:0
                     progress:^(NSInteger receivedSize, NSInteger expectedSize) {
                         // progression tracking code
                     }
                     completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
                         if (image) {
                             // Code to resize
                             //After resizing
                             cell.imageView.image = resizedImage;
                         }
                     }];
Sign up to request clarification or add additional context in comments.

7 Comments

Ok I got it working, howeever I would also like it to resize the images like they did before, how would I be able to manipulate the images before I show them in the table? Also, how can I update the table automatically, so you don't have to scroll the cell out and then into view for it to load the image into the cell?
btw, thanks for the heads up about the NSObject subclass, I never really understood what a NSObject was, now I know ;)
Also, how can I update the table automatically, so you don't have to scroll the cell out and then into view for it to load the image into the cell? Sorry but I did not understand this? I edited my answer to show where and how to resize images. @Chikara
Thanks! Howeever, what about the placeholder image? Right now I am creating an UIImage and setting the cell.imageView.image to that, until the images have loaded, how would you suggest to go about that when using the SDWebImageManager?
To elaborate on the part about updating the table with the images - What I mean is that the images does not load into the cells by themselves, you have to scroll the cells into view, then out of the view and then in again. This is because when the cells appear on the screen, the code to download the images starts, however no code ever runs to update the tableview with the images, so the cells must exit and reenter the view for them to update with the pictures in place.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.