我有一个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;
}
答案 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:
此外,您可以做一些事情来优化图像加载:
在配方实体中创建缩略图属性,以存储要在集合视图单元格中使用的较小版本的图像。使该图像不大于RecipeCell的最大宽度和高度。然后,在上面的块中使用该属性:
UIImage *image = recipe.thumbnail;
对主图像使用外部存储。我建议您尝试从图像属性中选择“存储在外部记录文件中”:
使用此选项可让Core Data为您管理外部文件存储。或者,您可以只存储图像路径并按照@HAS的建议将文件存储在磁盘上。以下是讨论这些选项的问题:Storing UIImage in Core Data with the new External Storage flag
如果您使用“存储在外部记录文件中”选项,请考虑将主图像移动到与食谱有关系的单独照片实体:食谱< - >照片在这种情况下,image
属性将位于Photo实体中。这是为了在跟随关系之前不会将它们加载到内存中。
此处还讨论了在核心数据中存储图像的最佳方法:How to store an image in core data
更新2:
如果滚动仍然不顺畅,请考虑使用@DavidH建议的NSCache。这里有一个示例实现:Caching an Image and UICollectionView。事实上,即使滚动已经顺利进行,这似乎也是正确的做法。
希望这有帮助。
答案 1 :(得分:1)
这里有很多内容,但是让我高度了解一种方法,让这项工作更好。
首先,如果可以的话,预取collectionView所需的第一组图像,以便在第一次显示视图时手头有它们。
获得这些后,您可以尝试在后台开始获取更多内容,或者选择在集合视图完成显示第一组时(您必须在此处进行实验)。
这个想法是,一旦你有东西向用户显示,你就会尝试立即预取“难以获取”的项目,然后缓存它们,希望你能比用户可以滚动它们更快地获取它们。
一旦你在后台得到一些东西,把它放到一个NSCache中,并在你的例程中查看单元格,在NSCache中寻找图像,如果它应用于单元格。如果它不在那里,那么只需将UIImageView的背景变为灰色(或某种颜色)。
如果用户滚动的速度比检索图像的速度快,则可以在从背景(和主线程)接收图像的方法中查看当前可见的单元格,如果有任何图像缺失您刚刚检索到的图像,将它应用到细胞中。
如果用户滚动缓慢,您可能会保持在曲线前方。如果它们滚动得非常快,它们将会看到空的图像视图,但是当它们向后滚动时,这些视图将被填充。
使用NSCache非常有效,因为系统会为它提供大量内存(如果可用),如果需要内存,它只会从缓存中删除项目,需要您再次获取它们。