Sto usando UICollectionView
in un'app che mostra un bel po 'di foto (50-200) e ho problemi a far sì che sia scattante (per esempio scattante come l'app Foto).Come ottenere un UICollectionView scattante con molte immagini (50-200)?
Ho un UICollectionViewCell
personalizzato con un UIImageView
come sottoview. Sto caricando immagini dal filesystem, con UIImage.imageWithContentsOfFile:
, nelle UIImageViews all'interno delle celle.
Ho provato un paio di approcci ora ma sono stati bug o hanno avuto problemi di prestazioni.
NOTA: Sto usando RubyMotion, quindi scriverò qualsiasi frammento di codice in stile Ruby.
Prima di tutto, ecco la mia personalizzato classe UICollectionViewCell per riferimento ...
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
Approccio # 1
Mantenere le cose semplici ..
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
Scorrimento su/giù è orribile usando questo. È nervoso e lento.
Approccio # 2
aggiungere un po 'di caching tramite 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
Anche in questo caso, jumpy e lenta nel primo rotolo pieno, ma da allora è una navigazione tranquilla.
Approccio # 3
caricare le immagini in modo asincrono ... (e mantenere il 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
Questo sembrava promettente, ma c'è un bug che non riesco a rintracciare. Nella vista iniziale carica tutte le immagini sono la stessa immagine e mentre scorri blocchi interi carichi con un'altra immagine ma tutti hanno quell'immagine. Ho provato a debuggarlo ma non riesco a capirlo.
Ho anche provato a sostituire NSOperation con Dispatch::Queue.concurrent.async { ... }
ma sembra essere lo stesso.
Penso che questo sia probabilmente l'approccio corretto se potessi farlo funzionare correttamente.
Approccio # 4
In frustrazione ho deciso di appena precarico tutte le immagini come 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
Questo si è rivelato essere un po 'lento e nervosi nel primo scroll completo ma tutto bene dopo.
Sommario
Quindi, se qualcuno mi può puntare nella giusta direzione sarei davvero molto grato. Come funziona l'app Foto così bene?
Nota per chiunque altro: [La accettato] risposta risolto il mio problema di immagini che appaiono nelle cellule sbagliate ma ero ancora ricevendo lo scorrimento instabile anche dopo il ridimensionamento mie immagini per correggere dimensioni. Dopo aver spogliato il mio codice fino all'essenziale, alla fine ho scoperto che UIImage # imageWithContentsOfFile era il colpevole. Anche se stavo pre-riscaldando una cache di UIImages usando questo metodo, UIImage sembra fare una specie di cache pigra internamente. Dopo aver aggiornato il mio codice di riscaldamento della cache per utilizzare la soluzione dettagliata qui - stackoverflow.com/a/10664707/71810 - Finalmente ho avuto uno scrolling super veloce di 60FPS.
Sì, grazie! Non posso credere di non averlo visto.Al momento la performance è ancora in movimento, ma dai miei test credo che sia solo perché le mie immagini sono troppo grandi per le celle. – alistairholt
Nota per chiunque altro: questa risposta ha risolto il mio problema di immagini che appaiono nelle celle sbagliate, ma continuavo a ricevere scansioni discontinue anche dopo il ridimensionamento delle immagini per correggere le dimensioni. Dopo aver spogliato il mio codice fino all'essenziale, alla fine ho scoperto che UIImage # imageWithContentsOfFile era il colpevole. Anche se stavo pre-riscaldando una cache di UIImages usando questo metodo, UIImage sembra fare una specie di cache pigra internamente. Dopo aver aggiornato il mio codice di riscaldamento della cache per utilizzare la soluzione dettagliata qui - http://stackoverflow.com/a/10664707/71810 - Finalmente ho avuto uno scrolling super veloce di 60FPS. – alistairholt