Eu escrevi duas maneiras de sincronizar o carregamento de imagens dentro da minha célula UITableView. Nos dois casos, a imagem carregará bem, mas quando eu rolar a tabela, as imagens mudarão algumas vezes até que a rolagem termine e a imagem retorne à imagem correta. Eu não tenho idéia do por que isso está acontecendo.
#define kBgQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
- (void)viewDidLoad
{
[super viewDidLoad];
dispatch_async(kBgQueue, ^{
NSData* data = [NSData dataWithContentsOfURL: [NSURL URLWithString:
@"http://myurl.com/getMovies.php"]];
[self performSelectorOnMainThread:@selector(fetchedData:)
withObject:data waitUntilDone:YES];
});
}
-(void)fetchedData:(NSData *)data
{
NSError* error;
myJson = [NSJSONSerialization
JSONObjectWithData:data
options:kNilOptions
error:&error];
[_myTableView reloadData];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
// Return the number of rows in the section.
// Usually the number of items in your array (the one that holds your list)
NSLog(@"myJson count: %d",[myJson count]);
return [myJson count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
myCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
if (cell == nil) {
cell = [[myCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
}
dispatch_async(kBgQueue, ^{
NSData *imgData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://myurl.com/%@.jpg",[[myJson objectAtIndex:indexPath.row] objectForKey:@"movieId"]]]];
dispatch_async(dispatch_get_main_queue(), ^{
cell.poster.image = [UIImage imageWithData:imgData];
});
});
return cell;
}
... ...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
myCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
if (cell == nil) {
cell = [[myCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
}
NSURL* url = [NSURL URLWithString:[NSString stringWithFormat:@"http://myurl.com/%@.jpg",[[myJson objectAtIndex:indexPath.row] objectForKey:@"movieId"]]];
NSURLRequest* request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse * response,
NSData * data,
NSError * error) {
if (!error){
cell.poster.image = [UIImage imageWithData:data];
// do whatever you want with image
}
}];
return cell;
}
poster
? Isso é presumivelmente uma visualização de imagem em sua célula personalizada. Portanto, o que EXEC_BAD_ACCESS está fazendo é perfeitamente correto. Você está certo de que não deve usar a célula como repositório dos dados do modelo, mas acho que não é isso que ele está fazendo. Ele está apenas dando à célula personalizada o que ela precisa para se apresentar. Além disso, e como essa é uma questão mais sutil, eu ficaria cauteloso ao armazenar uma imagem em si mesma em sua matriz de modelos, apoiando sua exibição de tabela. É melhor usar um mecanismo de armazenamento em cache de imagem e seu objeto de modelo deve ser recuperado desse cache.Respostas:
Supondo que você esteja procurando uma solução tática rápida, o que você precisa fazer é garantir que a imagem da célula seja inicializada e também que a linha da célula ainda esteja visível, por exemplo:
O código acima aborda alguns problemas decorrentes do fato de a célula ser reutilizada:
Você não está inicializando a imagem da célula antes de iniciar a solicitação em segundo plano (o que significa que a última imagem da célula desenfileirada ainda estará visível enquanto a nova imagem estiver sendo baixada). Certifique-se
nil
daimage
propriedade de qualquer exibição de imagem, caso contrário você verá a tremulação das imagens.Uma questão mais sutil é que, em uma rede muito lenta, sua solicitação assíncrona pode não ser concluída antes que a célula role para fora da tela. Você pode usar o
UITableView
métodocellForRowAtIndexPath:
(para não confundir com oUITableViewDataSource
método com nome semelhantetableView:cellForRowAtIndexPath:
) para ver se a célula dessa linha ainda está visível. Este método retornaránil
se a célula não estiver visível.O problema é que a célula foi desativada quando o método assíncrono foi concluído e, pior, a célula foi reutilizada para outra linha da tabela. Ao verificar se a linha ainda está visível, você garante que não atualiza acidentalmente a imagem com a imagem de uma linha que rolou para fora da tela.
Um pouco não relacionado à questão em questão, eu ainda me sentia obrigado a atualizar isso para aproveitar as convenções modernas e a API, principalmente:
Use em
NSURLSession
vez de despachar-[NSData contentsOfURL:]
para uma fila em segundo plano;Use em
dequeueReusableCellWithIdentifier:forIndexPath:
vez dedequeueReusableCellWithIdentifier:
(mas certifique-se de usar o protótipo de célula ou classe de registro ou NIB para esse identificador); eEu usei um nome de classe que esteja em conformidade com as convenções de nomenclatura do cacau (ou seja, comece com a letra maiúscula).
Mesmo com essas correções, há problemas:
O código acima não está armazenando em cache as imagens baixadas. Isso significa que, se você rolar uma imagem para fora da tela e voltar à tela, o aplicativo poderá tentar recuperá-la novamente. Talvez você tenha sorte o suficiente para que os cabeçalhos de resposta do servidor permitam o cache razoavelmente transparente oferecido por
NSURLSession
eNSURLCache
, se não, você estará solicitando desnecessariamente o servidor e oferecendo um UX muito mais lento.Não estamos cancelando solicitações de células que rolam para fora da tela. Portanto, se você rolar rapidamente para a 100ª linha, a imagem dessa linha poderá ser acumulada atrás das solicitações das 99 linhas anteriores que nem estão mais visíveis. Você sempre quer ter prioridade nas solicitações de células visíveis para o melhor UX.
A correção mais simples que soluciona esses problemas é usar uma
UIImageView
categoria, como é fornecida com SDWebImage ou AFNetworking . Se desejar, você pode escrever seu próprio código para lidar com os problemas acima, mas é muito trabalhoso, e asUIImageView
categorias acima já fizeram isso por você.fonte
updateCell.poster.image = nil
paracell.poster.image = nil;
updateCell é chamado antes de ser declarado.AFNetworking
é definitivamente o caminho a percorrer. Eu sabia disso, mas estava com preguiça de usá-lo. Estou apenas admirando como o cache funciona com sua linha de código simples.[imageView setImageWithURL:<#(NSURL *)#> placeholderImage:<#(UIImage *)#>];
cellForRowAtIndexPath
resulta em uma tremulação nas imagens quando eu rolar rapidamente" e expliquei por que isso aconteceu e como corrigi-lo. Mas passei a explicar por que mesmo isso era insuficiente, descrevi alguns problemas mais profundos e argumentei por que seria melhor você usar com uma dessas bibliotecas para lidar com isso com mais graça (priorize solicitações de células visíveis, faça cache para evitar redes redundantes solicitações etc.). Não estou claro o que mais você esperava em resposta à pergunta "como interrompo as imagens trêmulas na minha exibição de tabela"./ * Eu fiz dessa maneira e também testei * /
Etapa 1 = Registrar a classe de célula personalizada (no caso de célula protótipo na tabela) ou ponta (no caso de ponta personalizada para célula personalizada) para tabela como esta no método viewDidLoad:
OU
Etapa 2 = Use o método "dequeueReusableCellWithIdentifier: forIndexPath:" do UITableView como este (para isso, você deve registrar a classe ou ponta):
fonte
Existem várias estruturas que resolvem esse problema. Apenas para citar alguns:
Rápido:
Objetivo-C:
fonte
SDWebImage
não resolve esse problema. Você é capaz de controlar quando a imagem é baixada, masSDWebImage
atribua a imagemUIImageView
sem solicitar permissão para fazer isso. Basicamente, o problema da pergunta ainda não foi resolvido com esta biblioteca.Swift 3
Escrevo minha própria implementação de luz para o carregador de imagens usando o NSCache. Nenhuma imagem de célula piscando!
ImageCacheLoader.swift
Exemplo de uso
fonte
Aqui está a versão rápida (usando o código C objetivo do @Nitesh Borad): -
fonte
A melhor resposta não é a maneira correta de fazer isso :(. Na verdade, você vinculou o indexPath ao modelo, o que nem sempre é bom. Imagine que algumas linhas foram adicionadas durante o carregamento da imagem. Agora, a célula para o indexPath fornecido existe na tela, mas a imagem não está mais correto! A situação é improvável e difícil de replicar, mas é possível.
É melhor usar a abordagem MVVM, ligar a célula ao viewModel no controlador e carregar a imagem no viewModel (atribuindo o sinal ReactiveCocoa com o método switchToLatest), depois assine esse sinal e atribua a imagem à célula! ;)
Você deve se lembrar de não abusar do MVVM. As visualizações precisam ser simples! Considerando que os ViewModels devem ser reutilizáveis! É por isso que é muito importante vincular o View (UITableViewCell) e o ViewModel no controlador.
fonte
UIImageView
solução de categoria que eu aconselho, não haverá esse problema em relação aos caminhos do índice.No meu caso, não foi devido ao cache de imagens (SDWebImage usado). Isso ocorreu devido à incompatibilidade de tags da célula personalizada com indexPath.row.
Em cellForRowAtIndexPath:
1) Atribua um valor de índice à sua célula personalizada. Por exemplo,
2) Na thread principal, antes de atribuir a imagem, verifique se a imagem pertence à célula correspondente, combinando-a com a tag.
fonte
Obrigado "Rob" .... Eu tive o mesmo problema com o UICollectionView e sua resposta me ajudou a resolver o meu problema. Aqui está o meu código:
fonte
mycell *updateCell = (id)[collectionView cellForItemAtIndexPath:indexPath];
nunca é nulo, então isso não tem efeito.visibleCells
desse modo, mas suspeito que o uso[collectionView cellForItemAtIndexPath:indexPath]
seja mais eficiente (e é por isso que você faz essa chamada em primeiro lugar).updateCell
não estánil
, mas não o usa. Você deve usá-lo não apenas para determinar se a célula de exibição de coleção ainda está visível, mas também deve usarupdateCell
dentro deste blococell
(que pode não ser mais válido). E, obviamente, se fornil
, você não precisa fazer nada (porque essa célula não está visível).fonte
Eu acho que você deseja acelerar o carregamento do seu celular no momento do carregamento da imagem no celular em segundo plano. Para isso, realizamos as seguintes etapas:
A verificação do arquivo existe no diretório do documento ou não.
Caso contrário, carregue a imagem pela primeira vez e salve-a no diretório de documentos do telefone. Se você não quiser salvar a imagem no telefone, poderá carregar as imagens das células diretamente no plano de fundo.
Agora o processo de carregamento:
Basta incluir:
#import "ManabImageOperations.h"
O código é como abaixo para uma célula:
ManabImageOperations.h:
ManabImageOperations.m:
Por favor, verifique a resposta e comente se ocorrer algum problema ....
fonte
Simplesmente mude,
Para dentro
fonte
Você pode simplesmente passar seu URL,
fonte
fonte