简体   繁体   English

异步加载图像,单元格中的图像错误

[英]Loading images asynchronously, wrong image in cell

I have a UICollectionView in which I am loading multiple images into. 我有一个UICollectionView ,我在其中加载多个图像。 From what Ive been reading, in order to match the correct image to each cell I need to subclass UIImageView and get the image there. 从我正在阅读的内容来看,为了将正确的图像与每个单元格匹配,我需要将UIImageView子类化并在那里获取图像。 Because every time I collectionView reloadData , some images duplicate and they are all out of order. 因为每次我使用collectionView reloadData ,一些图像都会重复,并且它们都是乱序的。 But I am unsure how to do this and haven't found any tutorials. 但我不确定如何做到这一点,并没有找到任何教程。 I am using Parse for a database. 我正在使用Parse作为数据库。

 - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
albumImageCell *cell = (albumImageCell *) [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];

     if (cell == nil) {
         cell = [[albumImageCell alloc]init];
     }

     PFObject *temp = [_dataArray objectAtIndex:indexPath.row];
     PFFile *file = [temp objectForKey:@"imageThumbnail"];

     if (![cell.hasImage isEqualToString:@"YES"]) {
         dispatch_async(imageQueue, ^{
             NSData *data = [file getData];
                 if (data) {
                     UIImage *image = [UIImage imageWithData:data];

                     dispatch_async(dispatch_get_main_queue(), ^(void){
                         cell.imageView.image = image;
                         cell.hasImage = @"YES";
                     });

                 }
         });
     }

     return cell;
}

One way to solve this is to re-query the collection view for the cell again once you're back on the main queue. 解决此问题的一种方法是在您返回主队列后再次查询单元的集合视图。 This code should work: 这段代码应该有效:

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
albumImageCell *cell = (albumImageCell *) [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];

     if (cell == nil) {
         cell = [[albumImageCell alloc]init];
     }

     PFObject *temp = [_dataArray objectAtIndex:indexPath.row];
     PFFile *file = [temp objectForKey:@"imageThumbnail"];

     if (![cell.hasImage isEqualToString:@"YES"]) {
         dispatch_async(imageQueue, ^{
             NSData *data = [file getData];
                 if (data) {
                     UIImage *image = [UIImage imageWithData:data];

                     dispatch_async(dispatch_get_main_queue(), ^(void){
                         // cellAgain will be the actual cell at that index path, if it is visible. 
                         // If it is not visible, cellAgain will be nil.
                         albumImageCell *cellAgain = [collectionView cellForItemAtIndexPath:indexPath];
                         cellAgain.imageView.image = image;
                         cellAgain.hasImage = @"YES";
                     });

                 }
         });
     }

     return cell;
}

I made a small 'tutorial' in answer to a this question . 我在回答这个问题时做了一个小的“教程”。 Although the question refers to Core Data, my answer applies to any data source so you should be able to fit it around your use case. 虽然问题涉及核心数据,但我的答案适用于任何数据源,因此您应该能够适应您的用例。

One thing you want to watch out for is the inner block, when you get back onto the main queue. 当你回到主队列时,你要注意的一件事是内部块。 Given that you have no idea how long it takes to get to that point, the cell may no longer be relevant to that image (could have been reused), so you need to do a couple of additional checks... 鉴于您不知道到达该点需要多长时间,该单元格可能不再与该图像相关(可能已被重用),因此您需要进行一些额外的检查......

(a) is the image still required? (a)仍然需要图像吗?

if ([[tableView indexPathsForVisibleRows] containsObject:indexPath])

(b) is that cell is the correct cell for the image? (b) 单元格是图像的正确单元格吗?

 UITableViewCell * correctCell = [self.tableView cellForRowAtIndexPath:indexPath];

Although this tutorial is still valid, I tend to abstract things further these days. 虽然本教程仍然有效,但我现在倾向于进一步抽象。 As the viewController has to deal with thread-unsafe entities like UIKit and Core Data, it is a good idea to keep all viewController code on the main thread. 由于viewController必须处理线程不安全的实体,如UIKit和Core Data,因此最好将所有 viewController代码保存在主线程上。 Background queue abstractions should take place at a lower level, preferably in the model code. 背景队列抽象应该在较低级别进行,优选地在模型代码中进行。

What I ended up doing was subclassing UIImaveView and then passing the image file in cellForRow 我最终做的是UIImaveView ,然后在cellForRow传递图像文件

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
albumImageCell *cell = (albumImageCell *) [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];

    if (cell == nil) {
        cell = [[albumImageCell alloc]init];
    }

    PFObject *temp = [_dataArray objectAtIndex:indexPath.row];
    PFFile *file = [temp objectForKey:@"imageThumbnail"];

    [cell.imageView setFile:file];

    return cell;
}  

And then in the customImageView - 然后在customImageView -

- (void) setFile:(PFFile *)file {

    NSString *requestURL = file.url; // Save copy of url locally (will not change in block)
    [self setUrl:file.url]; // Save copy of url on the instance
    self.image = nil;
    [file getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
        if (!error) {
            UIImage *image = [UIImage imageWithData:data];
            if ([requestURL isEqualToString:self.url]) {
                [self setImage:image];
                [self setNeedsDisplay];
            }
        } else {
            NSLog(@"Error on fetching file");
        }
    }];
}  

But this gets Data every time the user scrolls to a new cell. 但是每次用户滚动到新单元格时,这都会获取数据。 So Im still trying to figure out how to match a particular image to a cell, without getting data every time. 所以我仍然试图弄清楚如何将特定图像与单元格匹配,而不是每次都获取数据。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM