如何获得一个snappy UICollectionView有很多的图像(50-200)? [英] How to get a snappy UICollectionView with lots of images (50-200)?

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

问题描述

我在显示大量照片(50-200)的应用中使用 UICollectionView ,我遇到了问题,



我有一个自定义 UICollectionViewCell 和一个 UIImageView 作为子视图。我从文件系统中加载图片,并将 UIImage.imageWithContentsOfFile:加入到单元格内的UIImageViews中。



我已经尝试了很多方法,但是他们已经有bug或者有性能问题。



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


$ b

方法#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



添加一点缓存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

再次,第一次完整滚动跳跃和缓慢,从那时起,它的顺畅航行。



方法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
返回除非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

我无法追踪的错误。在初始视图加载所有的图像是相同的图像和滚动整个块加载与另一个图像,但都有该图像。我试过调试它,但我不能弄清楚。



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



我认为这可能是正确的方法,如果我可以得到



在挫折中,我决定采取行动。只需将所有图像预加载为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滚动。

解决方案

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



您分配给 @image 是整个集合视图类的私有变量,在您的操作块中。您应该可以更改:

  @image = UIImage.imageWithContentsOfFile(image_path)

  image = UIImage.imageWithContentsOfFile(image_path) 

并更改 @image 使用局部变量。我愿意打赌,问题是每次你创建一个操作块并分配给实例变量,你正在替换已经存在的东西。由于操作块如何出队的一些随机性,主队列异步回调获取相同的图像,因为它正在访问 @image 被分配的最后一次。



本质上, @image 就像一个全局变量,用于操作和异步回调块。 >

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).

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.

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

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

Approach #1

Keeping things simple..

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.

Approach #2

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.

Approach #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.

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.

Approach #4

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.

Summary

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


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.

解决方案

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

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)

to

image = UIImage.imageWithContentsOfFile(image_path)

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.

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

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

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