Я использую UICollectionView
в приложении, которое отображает довольно много фотографий (50-200), и у меня возникают проблемы с его быстрой работой (например, такой же быстрой, как приложение «Фотографии»).
У меня есть пользовательский UICollectionViewCell
с UIImageView
в качестве подвида. Я загружаю изображения из файловой системы с помощью 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.