Como obter um UICollectionView mal-humorado com muitas imagens (50-200)?
estou a usarUICollectionView
em um aplicativo que está exibindo muitas fotos (50-200) e estou tendo problemas para fazê-lo ser mal-humorado (tão rápido quanto o aplicativo Fotos, por exemplo).
Eu tenho um costumeUICollectionViewCell
com umUIImageView
como é subview. Estou carregando imagens do sistema de arquivos, comUIImage.imageWithContentsOfFile:
, no UIImageViews dentro das células.
Eu tentei algumas abordagens agora, mas elas estavam com problemas ou problemas de desempenho.
NOTA: Estou usando o RubyMotion, então vou escrever qualquer trecho de código no estilo Ruby.
Primeiro de tudo, aqui está minha classe UICollectionViewCell personalizada para referência ...
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
Abordagem nº 1
Mantendo as coisas simples ..
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
Rolando para cima / baixo é horrível usando isso. É nervoso e lento.
Abordagem nº 2
Adicione um pouco de cache 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
Novamente, nervoso e lento no primeiro pergaminho completo, mas a partir daí é bom velejar.
Abordagem nº 3
Carregue as imagens de forma assíncrona ... (e mantenha o cache)
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
Isso parecia promissor, mas há um bug que não consigo rastrear. Na visualização inicial, carregue todas as imagens com a mesma imagem e, conforme você rola, blocos inteiros são carregados com outra imagem, mas todos têm essa imagem. Eu tentei depurar, mas não consigo descobrir.
Eu também tentei substituir o NSOperation comDispatch::Queue.concurrent.async { ... }
mas isso parece ser o mesmo.
Eu acho que esta é provavelmente a abordagem correta, se eu pudesse fazê-lo funcionar corretamente.
Abordagem nº 4
Em frustração, decidi apenas pré-carregar todas as imagens como 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
Isto acabou por ser um pouco lento e nervoso no primeiro pergaminho completo, mas tudo bem depois disso.
Resumo
Então, se alguém puder me apontar na direção certa, eu ficaria muito grato de fato. Como o aplicativo Fotos faz issotão bem?
Observação para qualquer outra pessoa: A resposta [aceita] resolveu meu problema de imagens aparecendo nas células erradas, mas eu ainda estava com a rolagem instável mesmo depois de redimensionar minhas imagens para corrigir tamanhos. Depois de despir meu código para o essencial, acabei descobrindo que UIImage # imageWithContentsOfFile era o culpado. Mesmo que eu estivesse pré-aquecendo um cache de UIImages usando esse método, o UIImage parece fazer algum tipo de cache ocioso internamente. Depois de atualizar meu código de aquecimento do cache para usar a solução detalhada aqui -stackoverflow.com/a/10664707/71810 - Eu finalmente tive uma rolagem super suave de ~ 60FPS.