为了账号安全,请及时绑定邮箱和手机立即绑定

在UITableView单元格中从url加载异步图像-滚动时将图像更改为错误的图像

/ 猿问

在UITableView单元格中从url加载异步图像-滚动时将图像更改为错误的图像

千巷猫影 2019-07-06 13:30:49

在UITableView单元格中从url加载异步图像-滚动时将图像更改为错误的图像

我编写了两种方法来在我的UITableView单元格中异步加载图片。在这两种情况下,图像将加载良好,但当我滚动表时,图像将更改几次,直到滚动结束,图像将返回到正确的图像。我不知道为什么会这样。


#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;

}



查看完整描述

3 回答

?
慕工程0101907

假设您正在寻找快速的战术修复,您需要做的是确保单元图像已经初始化,并且该单元格的行仍然是可见的,例如:

- (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;}

上面的代码解决了单元格被重用这一事实所产生的一些问题:

  1. 在启动背景请求之前,您没有初始化单元格映像(这意味着在下载新图像时,脱队列单元格的最后一个图像仍然是可见的)。确保nil这个image属性,否则您将看到图像的闪烁。

  2. 一个更微妙的问题是,在一个非常慢的网络上,您的异步请求可能不会在单元格从屏幕上滚动之前完成。您可以使用UITableView方法cellForRowAtIndexPath:(不要与同名的UITableViewDataSource方法tableView:cellForRowAtIndexPath:)查看该行的单元格是否仍然可见。此方法将返回nil如果单元格不可见。

    问题是,当异步方法完成时,单元格已经滚动,更糟糕的是,该单元格已被重用到表的另一行。通过检查该行是否仍然可见,您将确保不会意外地用该图像更新已从屏幕上滚动的行的图像。

  3. 与眼前的问题有些无关,我仍然感到有必要对此进行更新,以利用现代的约定和API,特别是:

    • 使用NSURLSession而不是派遣-[NSData contentsOfURL:]到后台队列;

    • 使用dequeueReusableCellWithIdentifier:forIndexPath:而不是dequeueReusableCellWithIdentifier:(但请确保使用单元格原型或注册类或NIB作为标识符);

    • 我使用了一个符合可可命名公约(即以大写字母开头)。

即使进行了这些更正,也存在一些问题:

  1. 上面的代码没有缓存下载的图像。这意味着,如果你滚动一个图像离开屏幕,并返回到屏幕上,应用程序可能试图再次检索图像。也许您会很幸运,您的服务器响应头将允许由NSURLSessionNSURLCache,但如果不是,您将发出不必要的服务器请求,并提供一个慢得多的UX。

  2. 我们不会取消对滚动屏幕的单元格的请求。因此,如果您快速滚动到第100行,则该行的图像可能会被回溯到前面99行的请求后面,而这些请求甚至是不可见的。您总是希望确保为获得最佳UX而对可见单元格的请求进行优先排序。

解决这些问题的最简单的解决方法是使用UIImageView类别,如提供的SDWebImageAFNetwork..如果您愿意,您可以编写自己的代码来处理上述问题,但这需要大量的工作和上面的工作。UIImageView类别已经为你做了这件事。


查看完整回答
反对 回复 2019-07-06
?
跃然一笑

/*我已经这样做了,也测试了它*/

步骤1=注册自定义单元格类(对于表中的原型单元格)或对于表的nib(自定义单元格为自定义单元格),如viewDidLoad方法中的如下所示:

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

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

步骤2=使用UITableView的“deQueeReusableCellWithIdentifier:forIndexPath:”方法(为此,必须注册类或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;
        }


查看完整回答
反对 回复 2019-07-06

添加回答

回复

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信