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.

questionAnswers(2)

yourAnswerToTheQuestion