Carregamento de imagem assíncrona a partir do URL dentro de uma célula UITableView - a imagem muda para a imagem errada ao rolar

158

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;
}
Segev
fonte
5
Você está tentando armazenar informações nas células reais. Isso é ruim, muito ruim. Você deve armazenar informações na matriz n (ou algo semelhante) e depois exibi-las nas células. As informações neste caso são a UIImage real. Sim, carregue-o de forma assíncrona, mas carregue-o em uma matriz.
Fogmeister
1
@Fogmeister Você está se referindo 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.
Rob
1
Sim, exatamente o meu ponto. Observando a solicitação (mostrada na íntegra), ele está baixando a imagem de forma assíncrona e colocando-a diretamente no imageView na célula. (Assim, usando a célula para armazenar os dados, ou seja, a imagem). O que ele deveria fazer é referenciar um objeto e solicitar a imagem desse objeto (contido em uma matriz ou em algum lugar). Se o objeto ainda não tiver a imagem, ele deve retornar um espaço reservado e fazer o download da imagem. Quando a imagem estiver baixada e pronta para exibição, informe a tabela para que ela possa atualizar a célula (se estiver visível).
Fogmeister
1
O que ele está fazendo forçará o download toda vez que ele rolar para a célula na tabela. Depende dele se as imagens são armazenadas de forma persistente, mas pelo menos as armazene durante o tempo de vida da exibição da tabela.
Fogmeister
1
Exatamente: D Dessa forma, você só precisa buscar a imagem do URL uma vez. Você verá isso em coisas como o Facebook Friend Picker. Quando você inicia, todos os avatares são espaços reservados em cinza. Então, enquanto você rola, todos são preenchidos à medida que avança. Mas quando você voltar para uma célula mostrada anteriormente, ela exibirá instantaneamente a imagem já baixada.
Fogmeister

Respostas:

230

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:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    MyCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];

    cell.poster.image = nil; // or cell.poster.image = [UIImage imageNamed:@"placeholder.png"];

    NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://myurl.com/%@.jpg", self.myJson[indexPath.row][@"movieId"]]];

    NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (data) {
            UIImage *image = [UIImage imageWithData:data];
            if (image) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    MyCell *updateCell = (id)[tableView cellForRowAtIndexPath:indexPath];
                    if (updateCell)
                        updateCell.poster.image = image;
                });
            }
        }
    }];
    [task resume];

    return cell;
}

O código acima aborda alguns problemas decorrentes do fato de a célula ser reutilizada:

  1. 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 nilda imagepropriedade de qualquer exibição de imagem, caso contrário você verá a tremulação das imagens.

  2. 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 UITableViewmétodo cellForRowAtIndexPath:(para não confundir com o UITableViewDataSourcemétodo com nome semelhante tableView:cellForRowAtIndexPath:) para ver se a célula dessa linha ainda está visível. Este método retornará nilse 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.

  3. 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 NSURLSessionvez de despachar -[NSData contentsOfURL:]para uma fila em segundo plano;

    • Use em dequeueReusableCellWithIdentifier:forIndexPath:vez de dequeueReusableCellWithIdentifier:(mas certifique-se de usar o protótipo de célula ou classe de registro ou NIB para esse identificador); e

    • Eu 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:

  1. 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 NSURLSessione NSURLCache, se não, você estará solicitando desnecessariamente o servidor e oferecendo um UX muito mais lento.

  2. 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 UIImageViewcategoria, 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 as UIImageViewcategorias acima já fizeram isso por você.

Roubar
fonte
1
Obrigado. Eu acredito que você precisa editar sua resposta. updateCell.poster.image = nilpara cell.poster.image = nil;updateCell é chamado antes de ser declarado.
Segev
1
Meu aplicativo usa muito json, então 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 *)#>];
Segev 21/05
2
Tentei todas as opções acima e SDWebImage (na verdade parou aqui e nem precisava tentar o AFNetworking) e essa foi de longe a melhor escolha. Obrigado @Rob.
Mondousage
1
Quando terminar de carregar a imagem e atualizar a visualização da imagem, remova o indicador de atividade. O único truque é que você deve prever o que acontece se a célula sair da tela enquanto a imagem estiver sendo recuperada e a célula for reutilizada para outra linha. Você precisará detectar a presença de qualquer indicador de atividade existente e remover / atualizar não apenas suponha que a célula esteja livre de um indicador existente.
Rob
1
A pergunta original era "por que isso cellForRowAtIndexPathresulta 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".
Rob
15

/ * 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:

[self.yourTableView registerClass:[CustomTableViewCell class] forCellReuseIdentifier:@"CustomCell"];

OU

[self.yourTableView registerNib:[UINib nibWithNibName:@"CustomTableViewCell" bundle:nil] forCellReuseIdentifier:@"CustomCell"];

Etapa 2 = Use o método "dequeueReusableCellWithIdentifier: forIndexPath:" do UITableView como este (para isso, você deve registrar a classe ou ponta):

   - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
            CustomTableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:@"CustomCell" forIndexPath:indexPath];

            cell.imageViewCustom.image = nil; // [UIImage imageNamed:@"default.png"];
            cell.textLabelCustom.text = @"Hello";

            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                // retrive image on global queue
                UIImage * img = [UIImage imageWithData:[NSData dataWithContentsOfURL:     [NSURL URLWithString:kImgLink]]];

                dispatch_async(dispatch_get_main_queue(), ^{

                    CustomTableViewCell * cell = (CustomTableViewCell *)[tableView cellForRowAtIndexPath:indexPath];
                  // assign cell image on main thread
                    cell.imageViewCustom.image = img;
                });
            });

            return cell;
        }
Nitesh Borad
fonte
1
Não chamar cellForRowAtIndexPath no bloco final faz com que a coisa toda seja acionada uma segunda vez?
Mark Bridges
@ MarkBridges, Não. Na verdade, estou chamando o método cellForRowAtIndexPath do tableView aqui. Não se confunda com o método de fonte de dados do tableView com o mesmo nome. É necessário, pode ser chamado como [self tableView: tableView cellForRowAtIndexPath: indexPath]; Espero que isso esclareça sua confusão.
Nitesh Borad
14

Existem várias estruturas que resolvem esse problema. Apenas para citar alguns:

Rápido:

Objetivo-C:

kean
fonte
Por favor, adicione suas sugestões se houver outras estruturas dignas de consideração.
kean
3
Na verdade, SDWebImagenão resolve esse problema. Você é capaz de controlar quando a imagem é baixada, mas SDWebImageatribua a imagem UIImageViewsem solicitar permissão para fazer isso. Basicamente, o problema da pergunta ainda não foi resolvido com esta biblioteca.
Bartłomiej Semańczyk
O problema da pergunta era que o autor não estava verificando se a célula foi reutilizada ou não. Esse é um problema muito básico abordado por essas estruturas, incluindo SDWebImage.
kean
O SDWebImage está muito lento desde o iOS 8, era um dos meus quadros favoritos, mas agora estou começando a usar o PinRemoteImage, que funciona realmente bom.
112615 Joan Cardona
@ BartłomiejSemańczyk Você está certo, este problema não foi resolvido por SDWebimage
Jan
9

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

typealias ImageCacheLoaderCompletionHandler = ((UIImage) -> ())

class ImageCacheLoader {
    
    var task: URLSessionDownloadTask!
    var session: URLSession!
    var cache: NSCache<NSString, UIImage>!
    
    init() {
        session = URLSession.shared
        task = URLSessionDownloadTask()
        self.cache = NSCache()
    }
    
    func obtainImageWithPath(imagePath: String, completionHandler: @escaping ImageCacheLoaderCompletionHandler) {
        if let image = self.cache.object(forKey: imagePath as NSString) {
            DispatchQueue.main.async {
                completionHandler(image)
            }
        } else {
            /* You need placeholder image in your assets, 
               if you want to display a placeholder to user */
            let placeholder = #imageLiteral(resourceName: "placeholder")
            DispatchQueue.main.async {
                completionHandler(placeholder)
            }
            let url: URL! = URL(string: imagePath)
            task = session.downloadTask(with: url, completionHandler: { (location, response, error) in
                if let data = try? Data(contentsOf: url) {
                    let img: UIImage! = UIImage(data: data)
                    self.cache.setObject(img, forKey: imagePath as NSString)
                    DispatchQueue.main.async {
                        completionHandler(img)
                    }
                }
            })
            task.resume()
        }
    }
}

Exemplo de uso

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    
    let cell = tableView.dequeueReusableCell(withIdentifier: "Identifier")
    
    cell.title = "Cool title"

    imageLoader.obtainImageWithPath(imagePath: viewModel.image) { (image) in
        // Before assigning the image, check whether the current cell is visible
        if let updateCell = tableView.cellForRow(at: indexPath) {
            updateCell.imageView.image = image
        }
    }    
    return cell
}
Dmitrii Klassneckii
fonte
3
eu diria obrigado a você. mas o código tem um pequeno problema. se deixar dados = tentar? Data (contentsOf: url) {// substitua o URL pelo local. ajudaria muita gente.
Carl Hung
2
Com o código como está, você baixa o dobro do arquivo pela rede: uma vez no downloadTaks, uma vez com o Data (cntentsOf :). Você deve local do usuário no lugar do URL, porque a tarefa de download simplesmente efetua o download pela rede e grava os dados em um arquivo temporário e passa o localUrl (local no seu caso). Portanto, os dados precisam apontar para o URL local, para que ele apenas leia o arquivo.
Stéphane de Luca
No exemplo de uso, ele deveria ser "ImageCacheLoader.obtainImageWithPath (imagePath: viewModel.image) ......."?
Tim Kruger
não funcionará ao rolar muito rápido, as imagens serão trocadas muitas vezes devido à reutilização da célula.
Juan Boero
5

Aqui está a versão rápida (usando o código C objetivo do @Nitesh Borad): -

   if let img: UIImage = UIImage(data: previewImg[indexPath.row]) {
                cell.cardPreview.image = img
            } else {
                // The image isn't cached, download the img data
                // We should perform this in a background thread
                let imgURL = NSURL(string: "webLink URL")
                let request: NSURLRequest = NSURLRequest(URL: imgURL!)
                let session = NSURLSession.sharedSession()
                let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
                    let error = error
                    let data = data
                    if error == nil {
                        // Convert the downloaded data in to a UIImage object
                        let image = UIImage(data: data!)
                        // Store the image in to our cache
                        self.previewImg[indexPath.row] = data!
                        // Update the cell
                        dispatch_async(dispatch_get_main_queue(), {
                            if let cell: YourTableViewCell = tableView.cellForRowAtIndexPath(indexPath) as? YourTableViewCell {
                                cell.cardPreview.image = image
                            }
                        })
                    } else {
                        cell.cardPreview.image = UIImage(named: "defaultImage")
                    }
                })
                task.resume()
            }
Chathuranga Silva
fonte
3

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.

badeleux
fonte
1
Sim, meu caminho de índice "correção tática" (que eu não estava recomendando, mas era apenas a edição mais modesta para resolver o problema do OP) sofre com esse problema (mas apenas se a tableview continuar a ter linhas adicionadas / excluídas). E se esse fenômeno se manifestasse, eu poderia corrigir outras formas (em vez de procurar usando o mesmo caminho de índice, apenas modelo de consulta para a linha apropriada). Mas essa correção tática tem ainda mais problemas flagrantes (que descrevi acima) do que o que você levanta aqui. Se você usar a UIImageViewsolução de categoria que eu aconselho, não haverá esse problema em relação aos caminhos do índice.
Rob
2
Posso parecer um pouco pedante, mas invocar qualquer tipo de lógica do VIEW está abusando dessa arquitetura.
21134 badeleux
3

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,

cell.tag = indexPath.row

2) Na thread principal, antes de atribuir a imagem, verifique se a imagem pertence à célula correspondente, combinando-a com a tag.

dispatch_async(dispatch_get_main_queue(), ^{
   if(cell.tag == indexPath.row) {
     UIImage *tmpImage = [[UIImage alloc] initWithData:imgData];
     thumbnailImageView.image = tmpImage;
   }});
});
AG
fonte
2

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:

 if ([Dict valueForKey:@"ImageURL"] != [NSNull null])
    {
        cell.coverImageView.image = nil;
        cell.coverImageView.imageURL=nil;

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

            if ([Dict valueForKey:@"ImageURL"] != [NSNull null] )
            {
                dispatch_async(dispatch_get_main_queue(), ^{

                    myCell *updateCell = (id)[collectionView cellForItemAtIndexPath:indexPath];

                    if (updateCell)
                    {
                        cell.coverImageView.image = nil;
                        cell.coverImageView.imageURL=nil;

                        cell.coverImageView.imageURL=[NSURL URLWithString:[Dict valueForKey:@"ImageURL"]];

                    }
                    else
                    {
                        cell.coverImageView.image = nil;
                        cell.coverImageView.imageURL=nil;
                    }


                });
            }
        });

    }
    else
    {
        cell.coverImageView.image=[UIImage imageNamed:@"default_cover.png"];
    }
sneha
fonte
Para mim, mycell *updateCell = (id)[collectionView cellForItemAtIndexPath:indexPath];nunca é nulo, então isso não tem efeito.
arquivo
1
você pode verificar se sua célula está visível ou não por: for (mycell * updateCell em collectionView.visibleCells) {cellVisible = YES; } if (cellVisible) {cell.coverImageView.imageURL = [NSURL URLWithString: [dict valueForKey: @ "ImageURL"]]; } Funcionou para mim também
sneha
@sneha Sim, você pode verificar se é visível iterando visibleCellsdesse modo, mas suspeito que o uso [collectionView cellForItemAtIndexPath:indexPath]seja mais eficiente (e é por isso que você faz essa chamada em primeiro lugar).
27414 Rob
@sneha Além disso, a propósito, no seu exemplo de código nesta resposta, acima, você verifica se updateCellnã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 usar updateCelldentro deste bloco cell(que pode não ser mais válido). E, obviamente, se for nil, você não precisa fazer nada (porque essa célula não está visível).
27414 Rob
2
 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
{
        MyCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];

        cell.poster.image = nil; // or cell.poster.image = [UIImage imageNamed:@"placeholder.png"];

        NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://myurl.com/%@.jpg", self.myJson[indexPath.row][@"movieId"]]];

        NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            if (data) {
                UIImage *image = [UIImage imageWithData:data];
                if (image) {
                    dispatch_async(dispatch_get_main_queue(), ^{
                        MyCell *updateCell = (id)[tableView cellForRowAtIndexPath:indexPath];
                        if (updateCell)
                            updateCell.poster.image = image;
                    });
                }
            }
        }];
        [task resume];

        return cell;
    }
Dharmraj Vora
fonte
0

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:

  1. A verificação do arquivo existe no diretório do documento ou não.

  2. 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.

  3. Agora o processo de carregamento:

Basta incluir: #import "ManabImageOperations.h"

O código é como abaixo para uma célula:

NSString *imagestr=[NSString stringWithFormat:@"http://www.yourlink.com/%@",[dictn objectForKey:@"member_image"]];

        NSString *docDir=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)objectAtIndex:0];
        NSLog(@"Doc Dir: %@",docDir);

        NSString  *pngFilePath = [NSString stringWithFormat:@"%@/%@",docDir,[dictn objectForKey:@"member_image"]];

        BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:pngFilePath];
        if (fileExists)
        {
            [cell1.memberimage setImage:[UIImage imageWithContentsOfFile:pngFilePath] forState:UIControlStateNormal];
        }
        else
        {
            [ManabImageOperations processImageDataWithURLString:imagestr andBlock:^(NSData *imageData)
             {
                 [cell1.memberimage setImage:[[UIImage alloc]initWithData: imageData] forState:UIControlStateNormal];
                [imageData writeToFile:pngFilePath atomically:YES];
             }];
}

ManabImageOperations.h:

#import <Foundation/Foundation.h>

    @interface ManabImageOperations : NSObject
    {
    }
    + (void)processImageDataWithURLString:(NSString *)urlString andBlock:(void (^)(NSData *imageData))processImage;
    @end

ManabImageOperations.m:

#import "ManabImageOperations.h"
#import <QuartzCore/QuartzCore.h>
@implementation ManabImageOperations

+ (void)processImageDataWithURLString:(NSString *)urlString andBlock:(void (^)(NSData *imageData))processImage
{
    NSURL *url = [NSURL URLWithString:urlString];

    dispatch_queue_t callerQueue = dispatch_get_main_queue();
    dispatch_queue_t downloadQueue = dispatch_queue_create("com.myapp.processsmagequeue", NULL);
    dispatch_async(downloadQueue, ^{
        NSData * imageData = [NSData dataWithContentsOfURL:url];

        dispatch_async(callerQueue, ^{
            processImage(imageData);
        });
    });
  //  downloadQueue=nil;
    dispatch_release(downloadQueue);

}
@end

Por favor, verifique a resposta e comente se ocorrer algum problema ....

Manab Kumar Mal
fonte
0

Simplesmente mude,

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];
     });
 });

Para dentro

    dispatch_async(kBgQueue, ^{
         NSData *imgData = [NSData dataWithContentsOfURL:[NSURL URLWithString:   [NSString stringWithFormat:@"http://myurl.com/%@.jpg",[[myJson objectAtIndex:indexPath.row] objectForKey:@"movieId"]]]];
         cell.poster.image = [UIImage imageWithData:imgData];
         dispatch_async(dispatch_get_main_queue(), ^{
            [self.tableView reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
         });
     });
Sazzad Hissain Khan
fonte
0

Você pode simplesmente passar seu URL,

NSURL *url = [NSURL URLWithString:@"http://www.myurl.com/1.png"];
NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data,    NSURLResponse * _Nullable response, NSError * _Nullable error) {
    if (data) {
        UIImage *image = [UIImage imageWithData:data];
        if (image) {
            dispatch_async(dispatch_get_main_queue(), ^{
                    yourimageview.image = image;
            });
        }
    }
}];
[task resume];
Usuário558
fonte
posso saber o motivo?
precisa saber é o seguinte
-1
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    Static NSString *CellIdentifier = @"Cell";
    QTStaffViewCell *cell = (QTStaffViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    If (cell == nil)
    {

        NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@"QTStaffViewCell" owner:self options:nil];
        cell = [nib objectAtIndex: 0];

    }

    StaffData = [self.staffArray objectAtIndex:indexPath.row];
    NSString *title = StaffData.title;
    NSString *fName = StaffData.firstname;
    NSString *lName = StaffData.lastname;

    UIFont *FedSanDemi = [UIFont fontWithName:@"Aller" size:18];
    cell.drName.text = [NSString stringWithFormat:@"%@ %@ %@", title,fName,lName];
    [cell.drName setFont:FedSanDemi];

    UIFont *aller = [UIFont fontWithName:@"Aller" size:14];
    cell.drJob.text = StaffData.job;
    [cell.drJob setFont:aller];

    if ([StaffData.title isEqualToString:@"Dr"])
    {
        cell.drJob.frame = CGRectMake(83, 26, 227, 40);
    }
    else
    {
        cell.drJob.frame = CGRectMake(90, 26, 227, 40);

    }

    if ([StaffData.staffPhoto isKindOfClass:[NSString class]])
    {
        NSURL *url = [NSURL URLWithString:StaffData.staffPhoto];
        NSURLSession *session = [NSURLSession sharedSession];
        NSURLSessionDownloadTask *task = [session downloadTaskWithURL:url
                completionHandler:^(NSURL *location,NSURLResponse *response, NSError *error) {

      NSData *imageData = [NSData dataWithContentsOfURL:location];
      UIImage *image = [UIImage imageWithData:imageData];

      dispatch_sync(dispatch_get_main_queue(),
             ^{
                    cell.imageView.image = image;
              });
    }];
        [task resume];
    }
       return cell;}
Ravindra Kishan
fonte
2
Despejos de código sem qualquer explicação raramente são úteis. Por favor, considere editar esta resposta para fornecer algum contexto.
Chris