zoukankan      html  css  js  c++  java
  • uiimageview 异步加载图片

        dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){

            

            NSURL *url = [NSURL URLWithString: detailedActivity.pictures];

            NSData *data = [[NSData alloc] initWithContentsOfURL:url];

            

            dispatch_async(dispatch_get_main_queue(), ^(void){

                

                self.activityImageView.image = [[UIImage alloc]initWithData:data];

            });

        });

     

     

     

     

     

    本文转载至 http://stackoverflow.com/questions/16663618/async-image-loading-from-url-inside-a-uitableview-cell-image-changes-to-wrong

    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;
    }
    shareimprove this question
     
    3  
    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 7: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 
    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

    7 Answers

     
     
     
     
     
     
     

    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"];
        if (cell == nil) {
            cell = [[myCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
        }
    
        cell.poster.image = nil; // or cell.poster.image = [UIImage imageNamed:@"placeholder.png"];
    
        dispatch_async(kBgQueue, ^{
            NSData *imgData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://myurl.com/%@.jpg",[[myJson objectAtIndex:indexPath.row] objectForKey:@"movieId"]]]];
            if (imgData) {
                UIImage *image = [UIImage imageWithData:imgData];
                if (image) {
                    dispatch_async(dispatch_get_main_queue(), ^{
                        myCell *updateCell = (id)[tableView cellForRowAtIndexPath:indexPath];
                        if (updateCell)
                            updateCell.poster.image = image;
                    });
                }
            }
        });
        return cell;
    }

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

    1. You're not initializing the cell image before you initiating the background request (meaning that the last image for the dequeued cell will still be there). Reset that. That's the more significant problem here.

    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.

      This approach will also work with the sendAsynchronousRequest rendition, too.

    An even better fix, though, is to use a UIImageView category, such as is provided with SDWebImage or AFNetworking, which will, handle this more efficiently canceling the request in progress if the cell's image view has been reused for another cell. On a very slow network, if you scroll quickly through the first, for example, 50 images for the first 50 rows of the tableview, your implementation (which is one I used to advocate until I better appreciated this subtlety) will continue trying to load images that have since scrolled off the screen, making it very slow to respond to that 51st image that you need for the currently visible cell.

    These UIImageView categories also solve an additional problem with your code: cacheing. You really don't want to re-retrieve the image from the network when you scroll back and look at previous rows. Judicious use of NSCache (which those categories use) will dramatically improve performance and reduce redundant network requests.

    If you want, you can write your own imageview category, but it's a lot of work, and SDWebImage or AFNetworking has done this already for you.

    shareimprove this answer
     
        
    Thanks. I believe you need to edit your answer. updateCell.poster.image = nil tocell.poster.image = nil; updateCell is called before it's declared. –  Segev May 21 '13 at 7:09
        
    @EXEC_BAD_ACCESS quite right. Thanks. –  Rob May 21 '13 at 7:11
        
    @EXEC_BAD_ACCESS AFNetworking is a general networking class that eliminates a lot of uglyNSURLConnection 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 7:30 
    3  
    @Rob At this moment, I love you more than I love my girlfriend, I don't have one but I'm sure I would love you more than her if thats the case, I had this stupid issue for a month now, and i'm delaying it everytime, but thanks to you NO MORE DELAYING! I fixed it! Make sure to get my app, maybe in less than a week, its called Feelit, we would be happy to have you aboard! xD –  Albara Sep 15 '13 at 13:31 
    2  
    This is one of the most beautiful solutions so far! Thanks! –  jovanjovanovic Feb 27 '14 at 4:00
  • 相关阅读:
    puppet master/agent
    puppet单机模型
    Nginx MogileFS 配置
    mogilefs 安装与配置
    CMakeLists.txt
    下载安装MariaDB Galera 10.1
    BZOJ1295: [SCOI2009]最长距离
    BZOJ2375: 疯狂的涂色
    BZOJ1260: [CQOI2007]涂色paint
    BZOJ2789: [Poi2012]Letters
  • 原文地址:https://www.cnblogs.com/Camier-myNiuer/p/4689049.html
Copyright © 2011-2022 走看看