非常に多くの写真(50〜200)を表示しているアプリで使用UICollectionView
していますが、写真をスナッピーにするのに問題があります(たとえば、写真アプリのようにスナッピーです)。
サブビューとしての習慣UICollectionViewCell
があります。ファイルシステムから、セル内のUIImageViewsにUIImageView
画像を読み込んでいます。UIImage.imageWithContentsOfFile:
私は今かなりの数のアプローチを試しましたが、それらはバグがあるか、パフォーマンスの問題がありました。
注:私は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のスクロールが可能になりました。