Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/ios/95.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Ios 如何获得具有大量图像(50-200)的snappy UICollectionView?_Ios_Cocoa Touch_Ios6_Uicollectionview_Rubymotion - Fatal编程技术网

Ios 如何获得具有大量图像(50-200)的snappy UICollectionView?

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

我在一个显示大量照片(50-200张)的应用程序中使用了
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;
}