如何获得一个有很多图像(50-200)的快速UICollectionView?

Ali*_*olt 13 cocoa-touch ios rubymotion ios6 uicollectionview

我正在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
Run Code Online (Sandbox Code Playgroud)

方法#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
Run Code Online (Sandbox Code Playgroud)

使用此功能可以向上/向下滚动.这是跳跃和缓慢.

方法#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
Run Code Online (Sandbox Code Playgroud)

再次,在第一个完整的卷轴上跳跃和缓慢,但从那时起它一帆风顺.

方法#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
Run Code Online (Sandbox Code Playgroud)

这看起来很有希望,但有一个我无法追查的错误.在初始视图加载时,所有图像都是相同的图像,当您滚动整个块加载另一个图像但所有图像都有.我已经尝试过调试但我无法弄明白.

我也试过替换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
Run Code Online (Sandbox Code Playgroud)

事实证明,在第一个完整的卷轴中有点慢和跳跃,但在那之后一切都很好.

摘要

所以,如果有人能指出我正确的方向,我会非常感激.Photos应用程序如何做得这么好


其他人的注意事项:[已接受的]答案解决了我出现在错误单元格中的图像问题,但即使在将图像调整到正确尺寸后,我仍然会出现不连贯的滚动.在将我的代码剥离到最基本的要素后,我最终发现UIImage#imageWithContentsOfFile是罪魁祸首.即使我使用这种方法预热UIImages的缓存,UIImage似乎在内部进行某种懒惰缓存.更新我的缓存加温代码后使用此处详述的解决方案 - stackoverflow.com/a/10664707/71810 - 我终于有了超级流畅的~60FPS滚动.

Jon*_*enn 7

我认为方法#3是最好的方法,我想我可能已经发现了这个错误.

您要@image在操作块中为整个集合视图类分配一个私有变量.你可能应该改变:

@image = UIImage.imageWithContentsOfFile(image_path)
Run Code Online (Sandbox Code Playgroud)

image = UIImage.imageWithContentsOfFile(image_path)
Run Code Online (Sandbox Code Playgroud)

并更改所有引用@image以使用局部变量.我愿意打赌问题在于,每次创建操作块并分配给实例变量时,您都要替换已经存在的内容.由于操作块如何出列的一些随机性,主队列异步回调正在获取相同的图像,因为它正在访问上次@image分配的时间.

本质上,@image它类似于操作的全局变量和异步回调块.

  • 其他人的注意事项:这个答案解决了我出现在错误单元格中的图像问题,但即使在将图像调整到正确尺寸后,我仍然会出现不连贯的滚动.在将我的代码剥离到最基本的要素后,我最终发现UIImage#imageWithContentsOfFile是罪魁祸首.即使我使用这种方法预热UIImages的缓存,UIImage似乎在内部进行某种懒惰缓存.更新我的缓存加温代码后使用此处详述的解决方案 - http://stackoverflow.com/a/10664707/71810 - 我终于有了超级流畅的~60FPS滚动. (4认同)