Ios 如何获得具有大量图像(50-200)的snappy UICollectionView?
我在一个显示大量照片(50-200张)的应用程序中使用了Ios 如何获得具有大量图像(50-200)的snappy UICollectionView?,ios,cocoa-touch,ios6,uicollectionview,rubymotion,Ios,Cocoa Touch,Ios6,Uicollectionview,Rubymotion,我在一个显示大量照片(50-200张)的应用程序中使用了UICollectionView,但我在如何使其快速(例如,与照片应用程序一样快速)方面遇到了问题 我有一个自定义的UICollectionViewCell,它的子视图是UIImageView。我正在将文件系统中带有UIImage.imageWithContentsOfFile:的图像加载到单元格内的UIImageView中 我现在已经尝试了很多方法,但它们要么有缺陷,要么有性能问题 注意:我使用的是RubyMotion,所以我将以Ruby
UICollectionView
,但我在如何使其快速(例如,与照片应用程序一样快速)方面遇到了问题
我有一个自定义的UICollectionViewCell
,它的子视图是UIImageView
。我正在将文件系统中带有UIImage.imageWithContentsOfFile:
的图像加载到单元格内的UIImageView中
我现在已经尝试了很多方法,但它们要么有缺陷,要么有性能问题
注意:我使用的是RubyMotion,所以我将以Ruby风格编写所有代码片段
首先,这里是我的自定义UICollectionViewCell类供参考
class CustomCell < UICollectionViewCell
def initWithFrame(rect)
super
@image_view = UIImageView.alloc.initWithFrame(self.bounds).tap do |iv|
iv.contentMode = UIViewContentModeScaleAspectFill
iv.clipsToBounds = true
iv.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth
self.contentView.addSubview(iv)
end
self
end
def prepareForReuse
super
@image_view.image = nil
end
def image=(image)
@image_view.image = image
end
end
使用此选项向上/向下滚动非常可怕。它又快又慢
方法#2
通过NSCache添加一点缓存
def viewDidLoad
...
@images_cache = NSCache.alloc.init
...
end
def collectionView(collection_view, cellForItemAtIndexPath: index_path)
cell = collection_view.dequeueReusableCellWithReuseIdentifier(CELL_IDENTIFIER, forIndexPath: index_path)
image_path = @image_paths[index_path.row]
if cached_image = @images_cache.objectForKey(image_path)
cell.image = cached_image
else
image = UIImage.imageWithContentsOfFile(image_path)
@images_cache.setObject(image, forKey: image_path)
cell.image = image
end
end
再次,在第一个完整的卷轴上跳跃和缓慢,但从那时起,它是一帆风顺的
方法#3
异步加载图像。。。(并保留缓存)
这看起来很有希望,但有一个bug我找不到。在初始视图加载时,所有图像都是相同的图像,当您滚动整个块时,加载另一个图像,但所有图像都具有该图像。我试过调试它,但我想不出来
我还尝试用Dispatch::Queue.concurrent.async{…}
替换NSOperation,但结果似乎是一样的
如果我能让它正常工作,我认为这可能是正确的方法
方法#4
无奈之下,我决定将所有图像作为UIImages预加载
def viewDidLoad
...
@preloaded_images = @image_paths.map do |path|
UIImage.imageWithContentsOfFile(path)
end
...
end
def collectionView(collection_view, cellForItemAtIndexPath: index_path)
cell = collection_view.dequeueReusableCellWithReuseIdentifier(CELL_IDENTIFIER, forIndexPath: index_path)
cell.image = @preloaded_images[index_path.row]
end
这在第一个完整的卷轴中有点慢和跳跃,但之后一切都很好
摘要
所以,如果有人能给我指出正确的方向,我将非常感激。照片应用程序是如何做到这一点的
其他人注意:[被接受的]答案解决了我的图像出现在错误单元格中的问题,但即使在将图像大小调整到正确大小后,我仍然会不断滚动。在将我的代码剥离到基本要素之后,我最终发现UIImage#imageWithContentsOfFile是罪魁祸首。尽管我使用此方法对UIImage缓存进行了预热,但UIImage似乎在内部进行了某种惰性缓存。在更新缓存预热代码以使用此处详述的解决方案后,我终于实现了超平滑~60FPS的滚动。我认为这种方法#3是最好的方法,我想我可能已经发现了错误 您正在操作块中为整个集合视图类指定一个私有变量
@image
。您可能应该更改:
@image = UIImage.imageWithContentsOfFile(image_path)
到
并将@image
的所有引用更改为使用局部变量。我敢打赌,问题在于每次创建操作块并分配给实例变量时,都在替换已有的操作块。由于操作块出列方式的一些随机性,主队列异步回调正在返回相同的映像,因为它正在访问上次分配的@image
本质上,
@image
充当操作和异步回调块的全局变量。我找到的解决此问题的最佳方法是保留自己的图像缓存,并在视图上预热它将显示在后台线程上,如下所示:
- (void) warmThubnailCache
{
if (self.isWarmingThumbCache) {
return;
}
if ([NSThread isMainThread])
{
// do it on a background thread
[NSThread detachNewThreadSelector:@selector(warmThubnailCache) toTarget:self withObject:nil];
}
else
{
self.isWarmingThumbCache = YES;
NIImageMemoryCache *thumbCache = self.thumbnailImageCache;
for (GalleryImage *galleryImage in _galleryImages) {
NSString *cacheKey = galleryImage.thumbImageName;
UIImage *thumbnail = [thumbCache objectWithName:cacheKey];
if (thumbnail == nil) {
// populate cache
thumbnail = [UIImage imageWithContentsOfFile:galleryImage.thumbImageName];
[thumbCache storeObject:thumbnail withName:cacheKey];
}
}
self.isWarmingThumbCache = NO;
}
}
您也可以使用GCD而不是NSThread,但我选择了NSThread,因为一次只能运行其中一个,所以不需要队列。此外,如果你有一个基本上无限数量的图像,你将不得不比我更小心。你将不得不更聪明地加载用户的滚动位置周围的图像,而不是像我在这里做的所有图像。我可以这样做,因为对我来说,图像的数量是固定的
我还应该补充一点,您的collectionView:cellForItemAtIndexPath:应该使用缓存,如下所示:
- (PSUICollectionViewCell *)collectionView:(PSUICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"GalleryCell";
GalleryCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CellIdentifier
forIndexPath:indexPath];
GalleryImage *galleryImage = [_galleryManager.galleryImages objectAtIndex:indexPath.row];
// use cached images first
NIImageMemoryCache *thumbCache = self.galleryManager.thumbnailImageCache;
NSString *cacheKey = galleryImage.thumbImageName;
UIImage *thumbnail = [thumbCache objectWithName:cacheKey];
if (thumbnail != nil) {
cell.imageView.image = thumbnail;
} else {
UIImage *image = [UIImage imageWithContentsOfFile:galleryImage.thumbImageName];
cell.imageView.image = image;
// store image in cache
[thumbCache storeObject:image withName:cacheKey];
}
return cell;
}
当收到内存警告时,请确保缓存已清除,或者使用类似NSCache的方法。我正在使用一个名为Nimbus的框架,它有一个名为NIImageMemoryCache的东西,它允许我设置要缓存的最大像素数。很方便
除此之外,需要注意的一个问题是不要使用UIImage的“imageNamed”方法,它自己进行缓存,并且会给您带来内存问题。请改用“imageWithContentsOfFile”。是的,谢谢!真不敢相信我居然没看到。性能目前仍在滚动,但从我的测试来看,我相信这只是因为我的图像对于单元格来说太大了。请注意:这个答案解决了我的图像出现在错误单元格中的问题,但即使在将图像大小调整到正确大小后,我仍然会不断滚动。在将我的代码剥离到基本要素之后,我最终发现UIImage#imageWithContentsOfFile是罪魁祸首。尽管我使用此方法对UIImage缓存进行了预热,但UIImage似乎在内部进行了某种惰性缓存。在更新缓存预热代码以使用此处详述的解决方案之后--我终于实现了超平滑~60FPS的滚动。
image = UIImage.imageWithContentsOfFile(image_path)
- (void) warmThubnailCache
{
if (self.isWarmingThumbCache) {
return;
}
if ([NSThread isMainThread])
{
// do it on a background thread
[NSThread detachNewThreadSelector:@selector(warmThubnailCache) toTarget:self withObject:nil];
}
else
{
self.isWarmingThumbCache = YES;
NIImageMemoryCache *thumbCache = self.thumbnailImageCache;
for (GalleryImage *galleryImage in _galleryImages) {
NSString *cacheKey = galleryImage.thumbImageName;
UIImage *thumbnail = [thumbCache objectWithName:cacheKey];
if (thumbnail == nil) {
// populate cache
thumbnail = [UIImage imageWithContentsOfFile:galleryImage.thumbImageName];
[thumbCache storeObject:thumbnail withName:cacheKey];
}
}
self.isWarmingThumbCache = NO;
}
}
- (PSUICollectionViewCell *)collectionView:(PSUICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"GalleryCell";
GalleryCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CellIdentifier
forIndexPath:indexPath];
GalleryImage *galleryImage = [_galleryManager.galleryImages objectAtIndex:indexPath.row];
// use cached images first
NIImageMemoryCache *thumbCache = self.galleryManager.thumbnailImageCache;
NSString *cacheKey = galleryImage.thumbImageName;
UIImage *thumbnail = [thumbCache objectWithName:cacheKey];
if (thumbnail != nil) {
cell.imageView.image = thumbnail;
} else {
UIImage *image = [UIImage imageWithContentsOfFile:galleryImage.thumbImageName];
cell.imageView.image = image;
// store image in cache
[thumbCache storeObject:image withName:cacheKey];
}
return cell;
}