如何获得带有大量图像(50-200)的活泼 UICollectionView? [英] How to get a snappy UICollectionView with lots of images (50-200)?

查看:25
本文介绍了如何获得带有大量图像(50-200)的活泼 UICollectionView?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在一个显示大量照片 (50-200) 的应用程序中使用 UICollectionView 并且我在让它变得活泼时遇到问题(例如像照片应用程序一样活泼)).

I'm using UICollectionView in an app that is displaying quite a lot of photos (50-200) and I'm having issues getting it to be snappy (as snappy as Photos app for example).

我有一个自定义的 UICollectionViewCell 和一个 UIImageView 作为它的子视图.我正在使用 UIImage.imageWithContentsOfFile: 从文件系统将图像加载到单元格内的 UIImageViews 中.

I have a custom UICollectionViewCell with a UIImageView as it's subview. I'm loading images from the filesystem, with UIImage.imageWithContentsOfFile:, into the UIImageViews inside the cells.

我现在尝试了很多方法,但它们要么有问题,要么存在性能问题.

I've tried quite a few approaches now but they have either been buggy or had performance issues.

注意:我正在使用 RubyMotion,所以我会以 Ruby 风格编写任何代码片段.

NOTE: I'm using RubyMotion so I'll write any code snippets out in the Ruby-style.

首先,这是我的自定义 UICollectionViewCell 类供参考...

First of all, here's my custom UICollectionViewCell class for reference...

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

使用这个向上/向下滚动是可怕的.它跳动而缓慢.

Scrolling up/down is horrible using this. It's jumpy and slow.

方法 #2

通过 NSCache 添加一点缓存...

Add a bit of caching via 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

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

Again, jumpy and slow on the first full scroll but from then it's smooth sailing.

方法#3

异步加载图像...(并保留缓存)

Load the images asynchronously... (and keep the caching)

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

这看起来很有希望,但有一个我无法追踪的错误.在初始视图加载时,所有图像都是相同的图像,并且当您滚动整个块时会加载另一个图像,但都具有该图像.我试过调试它,但我无法弄清楚.

This looked promising but there is a bug that I can't track down. On the initial view load all of the images are the same image and as you scroll whole blocks load with another image but all have that image. I've tried debugging it but I can't figure it out.

我也试过用 Dispatch::Queue.concurrent.async { ... } 代替 NSOperation,但这似乎是一样的.

I've also tried substituting the NSOperation with Dispatch::Queue.concurrent.async { ... } but that seems to be the same.

如果我能让它正常工作,我认为这可能是正确的方法.

I think this is probably the correct approach if I could get it to work properly.

方法#4

无奈之下,我决定将所有图像预加载为 UIImages...

In frustration I decided to just pre-load all the images as 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

这在第一个完整的卷轴中变得有点缓慢和跳跃,但在那之后一切都很好.

This turned out to be a bit slow and jumpy in the first full scroll but all good after that.

总结

所以,如果有人能指出我正确的方向,我将不胜感激.照片应用是如何做到如此出色的?

So, if anyone can point me in the right direction I'd be most grateful indeed. How does Photos app do it so well?

其他人请注意:[已接受] 答案解决了我的图像出现在错误单元格中的问题,但即使在将图像大小调整为正确大小后,我的滚动仍然不稳定.在将我的代码剥离到最基本的部分后,我最终发现 UIImage#imageWithContentsOfFile 是罪魁祸首.尽管我使用这种方法预热了 UIImages 的缓存,但 UIImage 似乎在内部进行了某种惰性缓存.在更新我的缓存预热代码以使用此处详述的解决方案后 - stackoverflow.com/a/10664707/71810 - 我终于有超流畅的〜60FPS滚动.

Note for anyone else: [The accepted] answer resolved my issue of images appearing in the wrong cells but I was still getting choppy scrolling even after resizing my images to correct sizes. After stripping my code down to the bare essentials I eventually found that UIImage#imageWithContentsOfFile was the culprit. Even though I was pre-warming a cache of UIImages using this method, UIImage seems to do some kind of lazy caching internally. After updating my cache warming code to use the solution detailed here - stackoverflow.com/a/10664707/71810 - I finally had super smooth ~60FPS scrolling.

推荐答案

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

I think that approach #3 is the best way to go, and I think I may have spotted the bug.

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

You're assigning to @image, a private variable for the whole collection view class, in your operation block. You should probably change:

@image = UIImage.imageWithContentsOfFile(image_path)

image = UIImage.imageWithContentsOfFile(image_path)

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

And change all the references for @image to use the local variable. I'm willing to bet that the problem is that every time you create an operation block and assign to the instance variable, you are replacing what is already there. Due to some of the randomness of how the operation blocks are dequeued, the main queue async callback is getting the same image back because it is accessing the last time that @image was assigned.

本质上,@image 就像操作和异步回调块的全局变量.

In essence, @image acts like a global variable for the operation and async callback blocks.

这篇关于如何获得带有大量图像(50-200)的活泼 UICollectionView?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆