164

I've written two ways to async load pictures inside my UITableView cell. In both cases the image will load fine but when I'll scroll the table the images will change a few times until the scroll will end and the image will go back to the right image. I have no idea why this is happening.

#define kBgQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

- (void)viewDidLoad
{
    [super viewDidLoad];
    dispatch_async(kBgQueue, ^{
        NSData* data = [NSData dataWithContentsOfURL: [NSURL URLWithString:
                                                       @"http://myurl.com/getMovies.php"]];
        [self performSelectorOnMainThread:@selector(fetchedData:)
                               withObject:data waitUntilDone:YES];
    });
}

-(void)fetchedData:(NSData *)data
{
    NSError* error;
    myJson = [NSJSONSerialization
              JSONObjectWithData:data
              options:kNilOptions
              error:&error];
    [_myTableView reloadData];
}    

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    // Return the number of sections.
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    // Return the number of rows in the section.
    // Usually the number of items in your array (the one that holds your list)
    NSLog(@"myJson count: %d",[myJson count]);
    return [myJson count];
}
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{

        myCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
        if (cell == nil) {
            cell = [[myCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
        }

        dispatch_async(kBgQueue, ^{
        NSData *imgData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://myurl.com/%@.jpg",[[myJson objectAtIndex:indexPath.row] objectForKey:@"movieId"]]]];

            dispatch_async(dispatch_get_main_queue(), ^{
        cell.poster.image = [UIImage imageWithData:imgData];
            });
        });
         return cell;
}

... ...

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{

            myCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
            if (cell == nil) {
                cell = [[myCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
            }
    NSURL* url = [NSURL URLWithString:[NSString stringWithFormat:@"http://myurl.com/%@.jpg",[[myJson objectAtIndex:indexPath.row] objectForKey:@"movieId"]]];
    NSURLRequest* request = [NSURLRequest requestWithURL:url];


    [NSURLConnection sendAsynchronousRequest:request
                                       queue:[NSOperationQueue mainQueue]
                           completionHandler:^(NSURLResponse * response,
                                               NSData * data,
                                               NSError * error) {
                               if (!error){
                                   cell.poster.image = [UIImage imageWithData:data];
                                   // do whatever you want with image
                               }

                           }];
     return cell;
}
Wil Shipley
  • 9,343
  • 35
  • 59
Segev
  • 19,035
  • 12
  • 80
  • 152
  • 6
    You're trying to store information in the actual cells. This is bad, very bad. You should store information in n array (or something similar) and then display it in the cells. The information in this case is the actual UIImage. Yes load it asynchronously but load it into an array. – Fogmeister May 21 '13 at 07:00
  • 1
    @Fogmeister Are you referring to `poster`? That's presumably an imageview in his custom cell, so what EXEC_BAD_ACCESS is doing is perfectly right. You are correct that you should not use the cell as the repository for model data, but I don't think that's what he's doing. He's just giving the custom cell what it needs to present itself. Furthermore, and this is a more subtle issue, I would be wary about storing an image, itself, in your model array backing your tableview. It's better to use a image caching mechanism and your model object should retrieve from that cache. – Rob May 21 '13 at 14:23
  • 1
    Yes, exactly my point. Looking at the request (which is shown in full) he is downloading the image asynchronously and putting it directly into the imageView in the cell. (Thus using the cell to store the data, i.e. the image). What he should be doing is referencing an object and requesting the image from that object (contained in an array or somewhere). If the object doesn't yet have the image it should return a placeholder and download the image. Then when the image is downloaded and ready to display let the table know so it can update the cell (if it's visible). – Fogmeister May 21 '13 at 14:28
  • 1
    What he is doing will force the download every single time he scrolls to that cell in the table. Whether the images are stored persistently is up to him, but at least store them for the life time of the tableview. – Fogmeister May 21 '13 at 14:28
  • @Fogmeister So what I should be doing is inside `tableView:cellForRowAtIndexPath`get the current image from objectAtIndex of indexPath.row, if it's empty, make the request and download the picture into that place in the area. Next time the cell is visible the picture will load from the array. Something like that? Like @rob suggested AFNetworking gives me a full solution for downloading & cache related issues but your comments are still very interesting. – Segev May 21 '13 at 14:38
  • 1
    Exactly :D That way you only need to fetch the image from the URL once. You will see this on things like Facebook Friend Picker. When you start it all the avatars are grey placeholders. Then as you scroll they all fill in as it moves along. But then when you scroll back to a cell previously shown it will instantly show the already downloaded image. – Fogmeister May 21 '13 at 14:41
  • Yes, I edited my last comment. I'm already using cache in my current AFNetworking solution. Thanks a lot for your comments! – Segev May 21 '13 at 14:43
  • @Fogmeister Thanks. I agree with your diagnosis, that his current code would issue redundant network requests (hence the suggestion that he use `SDWebImage` or `AFNetworking`, which both cache). I was only objecting to the characterization that his approach was using the cell to "store" the image (because he's not; he actually has the opposite problem, that he wasn't storing the image anywhere and he is discarding it when the cell is reused). So, while I might quibble re semantics, I think we're agreed regarding the basic solution. – Rob May 21 '13 at 16:18
  • Please, look my post in another question: http://stackoverflow.com/a/26147814/3052059 – Thomás Pereira Oct 01 '14 at 18:14
  • I have a really good example of how to handle this in UIImageLoader. Not only can you handle this case very easily, but UIImageLoader is a cache so images that have already been downloaded will be available immediately from disk or memory. https://github.com/gngrwzrd/UIImageLoader – gngrwzrd Mar 10 '16 at 00:42
  • @Rob Hi Rob! I understand libraries are great for this issue, but need to learn this. Can you take a look at this question please, thank you: https://stackoverflow.com/questions/44743591/download-and-cache-images-in-uitableviewcell – JEL Jun 25 '17 at 05:51

13 Answers13

242

Assuming you're looking for a quick tactical fix, what you need to do is make sure the cell image is initialized and also that the cell's row is still visible, e.g:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    MyCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];

    cell.poster.image = nil; // or cell.poster.image = [UIImage imageNamed:@"placeholder.png"];

    NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://myurl.com/%@.jpg", self.myJson[indexPath.row][@"movieId"]]];

    NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (data) {
            UIImage *image = [UIImage imageWithData:data];
            if (image) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    MyCell *updateCell = (id)[tableView cellForRowAtIndexPath:indexPath];
                    if (updateCell)
                        updateCell.poster.image = image;
                });
            }
        }
    }];
    [task resume];

    return cell;
}

The above code addresses a few problems stemming from the fact that the cell is reused:

  1. You're not initializing the cell image before initiating the background request (meaning that the last image for the dequeued cell will still be visible while the new image is downloading). Make sure to nil the image property of any image views or else you'll see the flickering of images.

  2. A more subtle issue is that on a really slow network, your asynchronous request might not finish before the cell scrolls off the screen. You can use the UITableView method cellForRowAtIndexPath: (not to be confused with the similarly named UITableViewDataSource method tableView:cellForRowAtIndexPath:) to see if the cell for that row is still visible. This method will return nil if the cell is not visible.

    The issue is that the cell has scrolled off by the time your async method has completed, and, worse, the cell has been reused for another row of the table. By checking to see if the row is still visible, you'll ensure that you don't accidentally update the image with the image for a row that has since scrolled off the screen.

  3. Somewhat unrelated to the question at hand, I still felt compelled to update this to leverage modern conventions and API, notably:

    • Use NSURLSession rather than dispatching -[NSData contentsOfURL:] to a background queue;

    • Use dequeueReusableCellWithIdentifier:forIndexPath: rather than dequeueReusableCellWithIdentifier: (but make sure to use cell prototype or register class or NIB for that identifier); and

    • I used a class name that conforms to Cocoa naming conventions (i.e. start with the uppercase letter).

Even with these corrections, there are issues:

  1. The above code is not caching the downloaded images. That means that if you scroll an image off screen and back on screen, the app may try to retrieve the image again. Perhaps you'll be lucky enough that your server response headers will permit the fairly transparent caching offered by NSURLSession and NSURLCache, but if not, you'll be making unnecessary server requests and offering a much slower UX.

  2. We're not canceling requests for cells that scroll off screen. Thus, if you rapidly scroll to the 100th row, the image for that row could be backlogged behind requests for the previous 99 rows that aren't even visible anymore. You always want to make sure you prioritize requests for visible cells for the best UX.

The simplest fix that addresses these issues is to use a UIImageView category, such as is provided with SDWebImage or AFNetworking. If you want, you can write your own code to deal with the above issues, but it's a lot of work, and the above UIImageView categories have already done this for you.

Cezar
  • 55,636
  • 19
  • 86
  • 87
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • 1
    Thanks. I believe you need to edit your answer. `updateCell.poster.image = nil` to `cell.poster.image = nil;` updateCell is called before it's declared. – Segev May 21 '13 at 07:09
  • @EXEC_BAD_ACCESS AFNetworking is a general networking class that eliminates a lot of ugly `NSURLConnection` programming. `SDWebImage` is a smaller framework, focusing primarily on images being retrieved from the web. Both do a pretty good job on their `UIImageView` categories. – Rob May 21 '13 at 07:30
  • 1
    My app uses a lot of json so `AFNetworking` is definitely the way to go. I knew about it but was to lazy to use it. I'm just admiring how the cacheing works with their simple line of code. `[imageView setImageWithURL:<#(NSURL *)#> placeholderImage:<#(UIImage *)#>];` – Segev May 21 '13 at 07:46
  • I always get `nil` on `myCell *updateCell = (id)[tableView cellForRowAtIndexPath:indexPath];` even though the tableview has not scrolled at all and all cells are clearly visible. :-/ any reason here? I am getting the image url from a remote json file so I have one step before the actual image download in there. – Jonny Mar 06 '14 at 02:04
  • @Jonny That's got to be something simple like `indexPath` or `tableView` are not what you think they are. I'd set a breakpoint there and carefully examine all the variables and see what's what. If you don't have any luck, post a new question on S.O. – Rob Mar 06 '14 at 02:21
  • I checked that neither indexPath nor tableView are nil. I really call those similarly to above, right in the delegate method (still, inside a block). I find no other definitions of said variables. I tried another method `if ([tableView.indexPathsForVisibleRows containsObject:indexPath]) {` which works mostly OK, but it seems that table views load some cells "ahead of time" while scrolling, before cells are really visible, and sometimes the loading of cached images happen too fast and return before indexPathsForVisibleRows includes the actual row. – Jonny Mar 06 '14 at 02:30
  • @Jonny Like I said, it probably makes sense to post your own question with more complete code samples, a little more context about your particular scenario, etc. – Rob Mar 06 '14 at 02:48
  • I am getting Unknown identifier "poster". and Undeclared identifier kBgQueue – Rahul Jan 28 '15 at 12:09
  • The original question used a custom cell with a `UIImageView` called `poster`. You have to replace that reference with whatever custom `UIImageView` property you added to your custom cell. Also, if you look at the original question, `kBgQueue` was defined there. – Rob Jan 28 '15 at 12:45
  • 2
    Tried all of the above and SDWebImage (actually stopped here and didn't even need to try AFNetworking) and this was by far the best choice. Thanks @Rob. – mondousage Oct 22 '15 at 21:12
  • A quick workaround for the second issue you pointed out is to also update the array that holds the data with the image data and just check the array to see if the data exists, also you need to add a `[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];` after updating the cell in the completion block – G_Money Jan 02 '16 at 22:28
  • Yep, that's another good approach, especially, if the cell layout changed. – Rob Jan 02 '16 at 23:56
  • additional Q. I was use loadingIndicator when loading image on a cell each. how to catch finish loading image? because I will hide the indicator on the cell. – Kernelzero Jun 16 '16 at 05:17
  • 1
    When you finish loading the image and update the image view, remove the activity indicator. The only trick is that you have to anticipate what happens if the cell scrolls out of view while the image is being retrieved and the cell is reused for another row, you'll have to detect the presence of any existing activity indicator and remove/update it, not just assume the cell is free of an existing indicator. – Rob Jun 16 '16 at 07:25
  • seriously? this "best" answer is totally useless for UITableViewCell – Gargo Sep 07 '16 at 08:14
  • what does this answer actually answer? how to send a lot of extra requests (depending on how long user scrolls the table) and how to call `cellForRowAtIndexPath:` while you should avoid that? – Gargo Sep 07 '16 at 19:34
  • 1
    The original question was "why does this `cellForRowAtIndexPath` result in a flickering of images when I scroll quickly" and I explained why that happened as well as how to fix it. But I went on to explain why even that was insufficient, described a few deeper problems, and argued why you'd be better off using with one of those libraries to handle this more gracefully (prioritize requests for visible cells, caching to avoid redundant network requests, etc.). I'm unclear what else you expected in response to the question of "how do I stop the flickering images in my table view". – Rob Sep 07 '16 at 20:09
  • I used Rob's "quick and dirty" fix at the end of a long day to have image views in a collection view working without much effort. Then I doubled back this morning and took Segev's suggestion of using [imageView setImageWithURL... The quick and dirty fix had a visible lag before loading the images (completely acceptable for a quick solution). The setImageWithURL solution loads the images with no lag at all. Both answers helped me tremendously. – Carl Smith Oct 19 '16 at 18:43
  • @Rob, I understand the call to `dispatch_async(dispatch_get_main_queue(), ^{`. But should this code also have a call to `dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{` like in Nitesh's answer, so that the download is performed on the background thread? – Chucky Feb 18 '18 at 12:58
  • This answered my previous comment https://stackoverflow.com/a/30855466/812013. "NSURLSession does its work in a background thread." – Chucky Feb 18 '18 at 14:00
15

/* I have done it this way, and also tested it */

Step 1 = Register custom cell class (in case of prototype cell in table) or nib (in case of custom nib for custom cell) for table like this in viewDidLoad method:

[self.yourTableView registerClass:[CustomTableViewCell class] forCellReuseIdentifier:@"CustomCell"];

OR

[self.yourTableView registerNib:[UINib nibWithNibName:@"CustomTableViewCell" bundle:nil] forCellReuseIdentifier:@"CustomCell"];

Step 2 = Use UITableView's "dequeueReusableCellWithIdentifier: forIndexPath:" method like this (for this, you must register class or nib) :

   - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
            CustomTableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:@"CustomCell" forIndexPath:indexPath];

            cell.imageViewCustom.image = nil; // [UIImage imageNamed:@"default.png"];
            cell.textLabelCustom.text = @"Hello";

            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                // retrive image on global queue
                UIImage * img = [UIImage imageWithData:[NSData dataWithContentsOfURL:     [NSURL URLWithString:kImgLink]]];

                dispatch_async(dispatch_get_main_queue(), ^{

                    CustomTableViewCell * cell = (CustomTableViewCell *)[tableView cellForRowAtIndexPath:indexPath];
                  // assign cell image on main thread
                    cell.imageViewCustom.image = img;
                });
            });

            return cell;
        }
Nitesh Borad
  • 4,583
  • 36
  • 51
  • 1
    Doesn't calling cellForRowAtIndexPath within the final block cause the whole thing to get fired a second time? – Mark Bridges May 12 '17 at 10:06
  • @MarkBridges, No. Actually I am calling tableView's cellForRowAtIndexPath method here. Don't get confused with tableView's datasource method with the same name. It required, it can be called like [self tableView: tableView cellForRowAtIndexPath: indexPath]; Hope this will clear your confusion. – Nitesh Borad Jul 12 '18 at 06:12
15

There are multiple frameworks that solve this problem. Just to name a few:

Swift:

Objective-C:

pkamb
  • 33,281
  • 23
  • 160
  • 191
kean
  • 1,561
  • 14
  • 22
  • Please add your suggestions if there are other frameworks worth consideration. – kean Sep 16 '15 at 07:32
  • 3
    Actually `SDWebImage` doesnt solve this problem. You are able to control when image is downloaded, but `SDWebImage` assign the image to `UIImageView` without asking you about permission to do this. Basically, the problem from the question is still not solved with this library. – Bartłomiej Semańczyk Dec 09 '15 at 07:38
  • The problem from the question was that the author was not checking whether the cell was reused or not. This is a _very_ basic problem which is addressed by those frameworks, including SDWebImage. – kean Dec 10 '15 at 15:23
  • SDWebImage is very laggy since iOS 8, it was one of my fav frameworks but now I'm starting using PinRemoteImage which works reaallly good. – Joan Cardona Dec 14 '15 at 11:23
  • @BartłomiejSemańczyk You are right, this issue doesnt solved by SDWebimage – Jan Apr 19 '16 at 04:27
10

Swift 3

I write my own light implementation for image loader with using NSCache. No cell image flickering!

ImageCacheLoader.swift

typealias ImageCacheLoaderCompletionHandler = ((UIImage) -> ())

class ImageCacheLoader {
    
    var task: URLSessionDownloadTask!
    var session: URLSession!
    var cache: NSCache<NSString, UIImage>!
    
    init() {
        session = URLSession.shared
        task = URLSessionDownloadTask()
        self.cache = NSCache()
    }
    
    func obtainImageWithPath(imagePath: String, completionHandler: @escaping ImageCacheLoaderCompletionHandler) {
        if let image = self.cache.object(forKey: imagePath as NSString) {
            DispatchQueue.main.async {
                completionHandler(image)
            }
        } else {
            /* You need placeholder image in your assets, 
               if you want to display a placeholder to user */
            let placeholder = #imageLiteral(resourceName: "placeholder")
            DispatchQueue.main.async {
                completionHandler(placeholder)
            }
            let url: URL! = URL(string: imagePath)
            task = session.downloadTask(with: url, completionHandler: { (location, response, error) in
                if let data = try? Data(contentsOf: url) {
                    let img: UIImage! = UIImage(data: data)
                    self.cache.setObject(img, forKey: imagePath as NSString)
                    DispatchQueue.main.async {
                        completionHandler(img)
                    }
                }
            })
            task.resume()
        }
    }
}

Usage example

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    
    let cell = tableView.dequeueReusableCell(withIdentifier: "Identifier")
    
    cell.title = "Cool title"

    imageLoader.obtainImageWithPath(imagePath: viewModel.image) { (image) in
        // Before assigning the image, check whether the current cell is visible
        if let updateCell = tableView.cellForRow(at: indexPath) {
            updateCell.imageView.image = image
        }
    }    
    return cell
}
Community
  • 1
  • 1
  • 3
    i would say thank you to you. but the code has a bit problem. if let data = try? Data(contentsOf: url) { // please replace url to location. it would help a lot of people. – Carl Hung May 18 '17 at 17:42
  • 2
    With the code as is, you download twice the file over the network: once in the downloadTaks, once with the Data(cntentsOf:). You must user location in place of url because the download task simply actually download over the network and write the data to a temporary file and passes you the localUrl (location in your case). So Data needs to point to the local Url so that it only read from the file. – Stéphane de Luca Oct 27 '17 at 15:10
  • In Usage Example, is it supposed to be "ImageCacheLoader.obtainImageWithPath( imagePath: viewModel.image)......."? – Tim Kruger Dec 18 '18 at 22:39
  • won't work when scrolling very fast, images will swap many times because of cell reusing. – Juan Boero Jul 09 '20 at 00:25
5

Here is the swift version (by using @Nitesh Borad objective C code) :-

   if let img: UIImage = UIImage(data: previewImg[indexPath.row]) {
                cell.cardPreview.image = img
            } else {
                // The image isn't cached, download the img data
                // We should perform this in a background thread
                let imgURL = NSURL(string: "webLink URL")
                let request: NSURLRequest = NSURLRequest(URL: imgURL!)
                let session = NSURLSession.sharedSession()
                let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
                    let error = error
                    let data = data
                    if error == nil {
                        // Convert the downloaded data in to a UIImage object
                        let image = UIImage(data: data!)
                        // Store the image in to our cache
                        self.previewImg[indexPath.row] = data!
                        // Update the cell
                        dispatch_async(dispatch_get_main_queue(), {
                            if let cell: YourTableViewCell = tableView.cellForRowAtIndexPath(indexPath) as? YourTableViewCell {
                                cell.cardPreview.image = image
                            }
                        })
                    } else {
                        cell.cardPreview.image = UIImage(named: "defaultImage")
                    }
                })
                task.resume()
            }
Mudith Chathuranga Silva
  • 7,253
  • 2
  • 50
  • 58
3

The best answer is not the correct way to do this :(. You actually bound indexPath with model, which is not always good. Imagine that some rows has been added during loading image. Now cell for given indexPath exists on screen, but the image is no longer correct! The situation is kinda unlikely and hard to replicate but it's possible.

It's better to use MVVM approach, bind cell with viewModel in controller and load image in viewModel (assigning ReactiveCocoa signal with switchToLatest method), then subscribe this signal and assign image to cell! ;)

You have to remember to not abuse MVVM. Views have to be dead simple! Whereas ViewModels should be reusable! It's why it's very important to bind View (UITableViewCell) and ViewModel in controller.

badeleux
  • 592
  • 5
  • 15
  • 1
    Yes, my index path "tactical fix" (which I wasn't recommending, but rather was just the most modest edit to resolve OP's problem) suffers from this issue (but only if the tableview continues to have rows added/deleted). And if this phenomenon manifested itself, I might fix that other ways (rather than looking up using same index path, just query model for appropriate row). But that tactical fix has even more egregious problems (which I describe above) than the one you raise here. If you use the `UIImageView` category solution I advise, then there is no such issue regarding index paths. – Rob Nov 20 '14 at 12:40
  • 2
    I may sound a little bit pedantic, but invoking any kind of logic from VIEW is abusing this architecture. – badeleux Nov 21 '14 at 09:09
3

In my case, it wasn't due to image caching (Used SDWebImage). It was because of custom cell's tag mismatch with indexPath.row.

On cellForRowAtIndexPath :

1) Assign an index value to your custom cell. For instance,

cell.tag = indexPath.row

2) On main thread, before assigning the image, check if the image belongs the corresponding cell by matching it with the tag.

dispatch_async(dispatch_get_main_queue(), ^{
   if(cell.tag == indexPath.row) {
     UIImage *tmpImage = [[UIImage alloc] initWithData:imgData];
     thumbnailImageView.image = tmpImage;
   }});
});
Alvin George
  • 14,148
  • 92
  • 64
2

Thank you "Rob"....I had same problem with UICollectionView and your answer help me to solved my problem. Here is my code :

 if ([Dict valueForKey:@"ImageURL"] != [NSNull null])
    {
        cell.coverImageView.image = nil;
        cell.coverImageView.imageURL=nil;

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

            if ([Dict valueForKey:@"ImageURL"] != [NSNull null] )
            {
                dispatch_async(dispatch_get_main_queue(), ^{

                    myCell *updateCell = (id)[collectionView cellForItemAtIndexPath:indexPath];

                    if (updateCell)
                    {
                        cell.coverImageView.image = nil;
                        cell.coverImageView.imageURL=nil;

                        cell.coverImageView.imageURL=[NSURL URLWithString:[Dict valueForKey:@"ImageURL"]];

                    }
                    else
                    {
                        cell.coverImageView.image = nil;
                        cell.coverImageView.imageURL=nil;
                    }


                });
            }
        });

    }
    else
    {
        cell.coverImageView.image=[UIImage imageNamed:@"default_cover.png"];
    }
sneha
  • 39
  • 5
  • For me, `mycell *updateCell = (id)[collectionView cellForItemAtIndexPath:indexPath];` is never nil, so this has no effect. – carbocation Jan 28 '14 at 19:40
  • 1
    you can check your cell is visible or not by : for(mycell *updateCell in collectionView.visibleCells) { cellVisible=YES; } if (cellVisible) { cell.coverImageView.imageURL=[NSURL URLWithString:[Dict valueForKey:@"ImageURL"]]; } It work for me as well – sneha Jan 29 '14 at 12:20
  • @sneha Yep, you can check to see it's visible by iterating through the `visibleCells` like that, but I suspect using `[collectionView cellForItemAtIndexPath:indexPath]` is more efficient (and it's why you do that call in the first place). – Rob Feb 27 '14 at 04:12
  • @sneha Also, by the way, in your code sample in this answer, above, you check to see if `updateCell` is not `nil`, but you then don't use it. You should use it not only to determine whether the collection view cell is still visible, but you should then use `updateCell` inside this block, not `cell` (which might not be valid any more). And obviously, if it's `nil`, you don't need to do anything (because this cell is not visible). – Rob Feb 27 '14 at 04:27
2
 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
{
        MyCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];

        cell.poster.image = nil; // or cell.poster.image = [UIImage imageNamed:@"placeholder.png"];

        NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://myurl.com/%@.jpg", self.myJson[indexPath.row][@"movieId"]]];

        NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            if (data) {
                UIImage *image = [UIImage imageWithData:data];
                if (image) {
                    dispatch_async(dispatch_get_main_queue(), ^{
                        MyCell *updateCell = (id)[tableView cellForRowAtIndexPath:indexPath];
                        if (updateCell)
                            updateCell.poster.image = image;
                    });
                }
            }
        }];
        [task resume];

        return cell;
    }
Kaushik Movaliya
  • 799
  • 11
  • 27
0

I think you want to speed up your cell loading at the time of image loading for cell in the background. For that we have done the following steps:

  1. Checking the file exists in the document directory or not.

  2. If not then loading the image for the first time, and saving it to our phone document directory. If you don't want to save the image in the phone then you can load cell images directlyin the background.

  3. Now the loading process:

Just include: #import "ManabImageOperations.h"

The code is like below for a cell:

NSString *imagestr=[NSString stringWithFormat:@"http://www.yourlink.com/%@",[dictn objectForKey:@"member_image"]];

        NSString *docDir=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)objectAtIndex:0];
        NSLog(@"Doc Dir: %@",docDir);

        NSString  *pngFilePath = [NSString stringWithFormat:@"%@/%@",docDir,[dictn objectForKey:@"member_image"]];

        BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:pngFilePath];
        if (fileExists)
        {
            [cell1.memberimage setImage:[UIImage imageWithContentsOfFile:pngFilePath] forState:UIControlStateNormal];
        }
        else
        {
            [ManabImageOperations processImageDataWithURLString:imagestr andBlock:^(NSData *imageData)
             {
                 [cell1.memberimage setImage:[[UIImage alloc]initWithData: imageData] forState:UIControlStateNormal];
                [imageData writeToFile:pngFilePath atomically:YES];
             }];
}

ManabImageOperations.h:

#import <Foundation/Foundation.h>

    @interface ManabImageOperations : NSObject
    {
    }
    + (void)processImageDataWithURLString:(NSString *)urlString andBlock:(void (^)(NSData *imageData))processImage;
    @end

ManabImageOperations.m:

#import "ManabImageOperations.h"
#import <QuartzCore/QuartzCore.h>
@implementation ManabImageOperations

+ (void)processImageDataWithURLString:(NSString *)urlString andBlock:(void (^)(NSData *imageData))processImage
{
    NSURL *url = [NSURL URLWithString:urlString];

    dispatch_queue_t callerQueue = dispatch_get_main_queue();
    dispatch_queue_t downloadQueue = dispatch_queue_create("com.myapp.processsmagequeue", NULL);
    dispatch_async(downloadQueue, ^{
        NSData * imageData = [NSData dataWithContentsOfURL:url];

        dispatch_async(callerQueue, ^{
            processImage(imageData);
        });
    });
  //  downloadQueue=nil;
    dispatch_release(downloadQueue);

}
@end

Please check the answer and comment if there is any problem occurs....

Manab Kumar Mal
  • 20,788
  • 5
  • 31
  • 43
0

Simply change,

dispatch_async(kBgQueue, ^{
     NSData *imgData = [NSData dataWithContentsOfURL:[NSURL URLWithString:   [NSString stringWithFormat:@"http://myurl.com/%@.jpg",[[myJson objectAtIndex:indexPath.row] objectForKey:@"movieId"]]]];
     dispatch_async(dispatch_get_main_queue(), ^{
        cell.poster.image = [UIImage imageWithData:imgData];
     });
 });

Into

    dispatch_async(kBgQueue, ^{
         NSData *imgData = [NSData dataWithContentsOfURL:[NSURL URLWithString:   [NSString stringWithFormat:@"http://myurl.com/%@.jpg",[[myJson objectAtIndex:indexPath.row] objectForKey:@"movieId"]]]];
         cell.poster.image = [UIImage imageWithData:imgData];
         dispatch_async(dispatch_get_main_queue(), ^{
            [self.tableView reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
         });
     });
Sazzad Hissain Khan
  • 37,929
  • 33
  • 189
  • 256
0

You can just pass your URL,

NSURL *url = [NSURL URLWithString:@"http://www.myurl.com/1.png"];
NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data,    NSURLResponse * _Nullable response, NSError * _Nullable error) {
    if (data) {
        UIImage *image = [UIImage imageWithData:data];
        if (image) {
            dispatch_async(dispatch_get_main_queue(), ^{
                    yourimageview.image = image;
            });
        }
    }
}];
[task resume];
Ashwini Chougale
  • 1,093
  • 10
  • 26
User558
  • 1,165
  • 1
  • 13
  • 37
-1
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    Static NSString *CellIdentifier = @"Cell";
    QTStaffViewCell *cell = (QTStaffViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    If (cell == nil)
    {

        NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@"QTStaffViewCell" owner:self options:nil];
        cell = [nib objectAtIndex: 0];

    }

    StaffData = [self.staffArray objectAtIndex:indexPath.row];
    NSString *title = StaffData.title;
    NSString *fName = StaffData.firstname;
    NSString *lName = StaffData.lastname;

    UIFont *FedSanDemi = [UIFont fontWithName:@"Aller" size:18];
    cell.drName.text = [NSString stringWithFormat:@"%@ %@ %@", title,fName,lName];
    [cell.drName setFont:FedSanDemi];

    UIFont *aller = [UIFont fontWithName:@"Aller" size:14];
    cell.drJob.text = StaffData.job;
    [cell.drJob setFont:aller];

    if ([StaffData.title isEqualToString:@"Dr"])
    {
        cell.drJob.frame = CGRectMake(83, 26, 227, 40);
    }
    else
    {
        cell.drJob.frame = CGRectMake(90, 26, 227, 40);

    }

    if ([StaffData.staffPhoto isKindOfClass:[NSString class]])
    {
        NSURL *url = [NSURL URLWithString:StaffData.staffPhoto];
        NSURLSession *session = [NSURLSession sharedSession];
        NSURLSessionDownloadTask *task = [session downloadTaskWithURL:url
                completionHandler:^(NSURL *location,NSURLResponse *response, NSError *error) {

      NSData *imageData = [NSData dataWithContentsOfURL:location];
      UIImage *image = [UIImage imageWithData:imageData];

      dispatch_sync(dispatch_get_main_queue(),
             ^{
                    cell.imageView.image = image;
              });
    }];
        [task resume];
    }
       return cell;}
Lucas Zamboulis
  • 2,494
  • 5
  • 24
  • 27
Ravindra Kishan
  • 183
  • 1
  • 6