如何获得,有很多图片(50-200)的瞬间UICollectionView?(How to get

2019-08-04 02:02发布

我使用UICollectionView在相当展示了许多照片(50-200)的应用程序,我有得到它的问题是活泼的(如活泼,例如照片应用程序)。

我有一个自定义UICollectionViewCellUIImageView ,因为它的子视图。 我从文件系统加载图像,与UIImage.imageWithContentsOfFile:进入细胞内的UIImageViews。

我现在已经尝试了不少方法,但他们要么被越野车或有性能问题。

注:我使用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

方法#1

让事情简单..

def collectionView(collection_view, cellForItemAtIndexPath: index_path)
  cell = collection_view.dequeueReusableCellWithReuseIdentifier(CELL_IDENTIFIER, forIndexPath: index_path)
  image_path = @image_paths[index_path.row]
  cell.image = UIImage.imageWithContentsOfFile(image_path)
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

加载图像异步......(并保持高速缓存)

def viewDidLoad
  ...
  @images_cache = NSCache.alloc.init
  @image_loading_queue = NSOperationQueue.alloc.init
  @image_loading_queue.maxConcurrentOperationCount = 3
  ...
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
    @operation = NSBlockOperation.blockOperationWithBlock lambda {
      @image = UIImage.imageWithContentsOfFile(image_path)
      Dispatch::Queue.main.async do
        return unless collectionView.indexPathsForVisibleItems.containsObject(index_path)
        @images_cache.setObject(@image, forKey: image_path)
        cell = collectionView.cellForItemAtIndexPath(index_path)
        cell.image = @image
      end
    }
    @image_loading_queue.addOperation(@operation)
  end
end

这看起来很有前途,但没有,我不能追查的错误。 在初始视图加载所有的图像是相同的图像,将滚动与其它图像整块负载,但都有这样的形象。 我试着调试它,但我不能弄明白。

我也试着代与的NSOperation Dispatch::Queue.concurrent.async { ... }但似乎是相同的。

我想,这可能是正确的做法,如果我能得到它的正常工作。

办法#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是罪魁祸首。 虽然我是预暖使用这种方法UIImages的高速缓存,UIImage的似乎做某种懒缓存的内部。 -更新我的缓存变暖代码,使用这里详述该溶液后stackoverflow.com/a/10664707/71810 -我终于有超光滑〜60FPS滚动。

Answer 1:

我认为这种做法#3是最好的方式,我想我可能已经发现的bug。

你分配给@image ,整个集合视图类私有变量,在你的操作块。 你或许应该改变:

@image = UIImage.imageWithContentsOfFile(image_path)

image = UIImage.imageWithContentsOfFile(image_path)

并改变所有引用的@image使用本地变量。 我敢打赌,这个问题是,每次你创建一个操作块,并分配给实例变量时,要更换什么是已经存在。 由于一些操作块如何离队的随机性,主要队列异步回调得到相同的图像回来,因为它是访问的最后一次@image分配。

从本质上说, @image就像运行和异步回调块的全局变量。



Answer 2:

我发现解决这个问题的最好办法是通过保持我自己的图像缓存和预加温,以便在viewWillAppear中在后台线程,就像这样:

- (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”代替。



文章来源: How to get a snappy UICollectionView with lots of images (50-200)?