如何使用iOS中的大图像?

时间:2013-11-18 02:52:05

标签: ios uiimageview uicollectionview uicollectionviewcell

我有一个UICollectionView,它显示了CoreData实体的一些数据。该实体具有许多属性,其中一个是图像(可转换类型)。

我的UICollectionView滚动不顺畅。我认为原因是图像属性中的大图片(从300到500k)。

以下是我cellForItemAtIndexPath的代码:

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    AppDelegate *d = (AppDelegate *)[[UIApplication sharedApplication] delegate];
    static NSString *CellIdentifier = @"cellRecipe";

    RecipeCell *cell = (RecipeCell *)[self.collectionView dequeueReusableCellWithReuseIdentifier:CellIdentifier forIndexPath:indexPath];

    cell.image.layer.borderColor = [UIColor blackColor].CGColor;
    cell.image.layer.borderWidth = 1.0f;

    Recipes *recipe = [recipesArray_ objectAtIndex:indexPath.item];

    if ([recipe.isPaid integerValue] == 0)
        [cell.freeImage setHidden:NO];
    else [cell.freeImage setHidden:YES];

    NSObject *object = [[NSUserDefaults standardUserDefaults] objectForKey:@"com.________.fullhomechef"];
    if ([object isEqual:[NSNumber numberWithBool:YES]]) {
        [cell.freeImage setHidden:YES];
    }

    if ([recipe.isFavorite integerValue] == 0) 
        [cell.favImage setImage:[UIImage imageNamed:@"toFavorite.png"]];
    else [cell.favImage setImage: [UIImage imageNamed:@"toFavorite_.png"]];

    if ([recipe.isFitness integerValue] == 1)
        [cell.fitImage setHidden:NO];
    else [cell.fitImage setHidden:YES];

    if ([d.currLang isEqualToString:@"ru"]) 
        [cell.recipeName setText: recipe.nameru];
    else [cell.recipeName setText: recipe.nameen];

    [cell.image setImage:recipe.image];

    int difficulty = [recipe.difficulty intValue];
    [cell.difficultyImage setImage: [mainTabController imageForRating:difficulty]];

    return cell;
}

我试图评论第[cell.image setImage:recipe.image];行,但滚动仍然是 间歇性的。

我做错了什么?可能有一些方法如何处理大图像?在以前的版本中,我使用了大小为50-70k的图像。

P.S。如果我耐心地将其向下滚动到最后,滚动会变得更加平滑。

在Guto Araujo的答案之后

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"cellRecipe";

    RecipeCell *cell = (RecipeCell *)[self.collectionView dequeueReusableCellWithReuseIdentifier:CellIdentifier forIndexPath:indexPath];

    cell.image.layer.borderColor = [UIColor blackColor].CGColor;
    cell.image.layer.borderWidth = 1.0f;

    Recipes *recipe = [fetchRecipes objectAtIndexPath:indexPath];

    if ([recipe.isPaid integerValue] == 0) {
        [cell.freeImage setHidden:NO];
    }
    else [cell.freeImage setHidden:YES];

    if (wasPaid == YES) {
        [cell.freeImage setHidden:YES];
    }

    if ([recipe.isFavorite integerValue] == 0) 
        [cell.favImage setImage:[UIImage imageNamed:@"toFavorite.png"] ];
    else [cell.favImage setImage: [UIImage imageNamed:@"toFavorite_.png"]];

    if ([recipe.isFitness integerValue] == 1)
        [cell.fitImage setHidden:NO];
    else [cell.fitImage setHidden:YES];

    if ([d.currLang isEqualToString:@"ru"]) {
        [cell.recipeName setText: recipe.nameru];
    } else [cell.recipeName setText:recipe.nameen];

    __weak typeof(self) weakSelf = self;
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        UIImage *image = recipe.image;

        dispatch_async(dispatch_get_main_queue(), ^{
            // Set via the main queue if the cell is still visible
            if ([weakSelf.collectionView.indexPathsForVisibleItems containsObject:indexPath]) {
                RecipeCell *cell =
                (RecipeCell *)[weakSelf.collectionView cellForItemAtIndexPath:indexPath];
                cell.image.image = image;
            }
        });
    }];
    [thumbnailQueue addOperation:operation];

    int difficulty = [recipe.difficulty intValue];
    [cell.difficultyImage setImage: [mainTabController imageForRating:difficulty]];

    return cell;
}

2 个答案:

答案 0 :(得分:4)

一种方法是使用操作队列在后台加载图像,如本UICollectionView tutorial by Bryan Hansen中所述,具体步骤为#45-47:

为操作队列添加属性:

@property (nonatomic, strong) NSOperationQueue *thumbnailQueue;

maxConcurrentOperationCount中初始化并配置viewDidLoad

self.thumbnailQueue = [[NSOperationQueue alloc] init];
self.thumbnailQueue.maxConcurrentOperationCount = 3; // Try different values for this variable

使用collectionView:cellForItemAtIndexPath:中的操作队列以异步方式从Core Data加载图像:

// Change [cell.image setImage:recipe.image] for the code below

__weak typeof(self) weakSelf = self;
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
    UIImage *image = recipe.image;

    dispatch_async(dispatch_get_main_queue(), ^{
        // Set cell.image via the main queue if the cell is still visible
        if ([weakSelf.collectionView.indexPathsForVisibleItems containsObject:indexPath]) {
            RecipeCell *cell =
                (RecipeCell *)[weakSelf.collectionView cellForItemAtIndexPath:indexPath];
            cell.image = image;
        }
    });
}];

[self.thumbnailQueue addOperation:operation];

更新1:

此外,您可以做一些事情来优化图像加载:

  1. 在配方实体中创建缩略图属性,以存储要在集合视图单元格中使用的较小版本的图像。使该图像不大于RecipeCell的最大宽度和高度。然后,在上面的块中使用该属性:

     UIImage *image = recipe.thumbnail;
    
  2. 对主图像使用外部存储。我建议您尝试从图像属性中选择“存储在外部记录文件中”:

    Store In External Record File

    使用此选项可让Core Data为您管理外部文件存储。或者,您可以只存储图像路径并按照@HAS的建议将文件存储在磁盘上。以下是讨论这些选项的问题:Storing UIImage in Core Data with the new External Storage flag

  3. 如果您使用“存储在外部记录文件中”选项,请考虑将主图像移动到与食谱有关系的单独照片实体:食谱< - >照片在这种情况下,image属性将位于Photo实体中。这是为了在跟随关系之前不会将它们加载到内存中。

    此处还讨论了在核心数据中存储图像的最佳方法:How to store an image in core data

    更新2:

  4. 如果滚动仍然不顺畅,请考虑使用@DavidH建议的NSCache。这里有一个示例实现:Caching an Image and UICollectionView。事实上,即使滚动已经顺利进行,这似乎也是正确的做法。

  5. 希望这有帮助。

答案 1 :(得分:1)

这里有很多内容,但是让我高度了解一种方法,让这项工作更好。

首先,如果可以的话,预取collectionView所需的第一组图像,以便在第一次显示视图时手头有它们。

获得这些后,您可以尝试在后台开始获取更多内容,或者选择在集合视图完成显示第一组时(您必须在此处进行实验)。

这个想法是,一旦你有东西向用户显示,你就会尝试立即预取“难以获取”的项目,然后缓存它们,希望你能比用户可以滚动它们更快地获取它们。

一旦你在后台得到一些东西,把它放到一个NSCache中,并在你的例程中查看单元格,在NSCache中寻找图像,如果它应用于单元格。如果它不在那里,那么只需将UIImageView的背景变为灰色(或某种颜色)。

如果用户滚动的速度比检索图像的速度快,则可以在从背景(和主线程)接收图像的方法中查看当前可见的单元格,如果有任何图像缺失您刚刚检索到的图像,将它应用到细胞中。

如果用户滚动缓慢,您可能会保持在曲线前方。如果它们滚动得非常快,它们将会看到空的图像视图,但是当它们向后滚动时,这些视图将被填充。

使用NSCache非常有效,因为系统会为它提供大量内存(如果可用),如果需要内存,它只会从缓存中删除项目,需要您再次获取它们。