Asynchroniczne ładowanie obrazu z adresu URL w komórce UITableView - podczas przewijania obraz zmienia się na nieprawidłowy

158

Napisałem dwa sposoby asynchronicznego ładowania obrazów w mojej komórce UITableView. W obu przypadkach obraz załaduje się dobrze, ale kiedy przewinę tabelę, obrazy zmienią się kilka razy, aż przewijanie się skończy, a obraz wróci do właściwego obrazu. Nie mam pojęcia, dlaczego tak się dzieje.

#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
źródło
5
Próbujesz przechowywać informacje w rzeczywistych komórkach. To jest złe, bardzo złe. Powinieneś przechowywać informacje w tablicy n (lub czymś podobnym), a następnie wyświetlać je w komórkach. Informacje w tym przypadku to rzeczywisty UIImage. Tak, załaduj go asynchronicznie, ale załaduj do tablicy.
Fogmeister
1
@Fogmeister Czy masz na myśli poster? Prawdopodobnie jest to widok obrazu w jego niestandardowej komórce, więc to, co robi EXEC_BAD_ACCESS, jest całkowicie słuszne. Masz rację, mówiąc, że nie powinieneś używać komórki jako repozytorium danych modelu, ale nie sądzę, że on to robi. Po prostu daje niestandardowej komórce to, czego potrzebuje, aby się zaprezentować. Co więcej, i jest to bardziej subtelna kwestia, byłbym ostrożny, jeśli chodzi o przechowywanie samego obrazu w tablicy modelu jako kopii zapasowej widoku tabeli. Lepiej jest użyć mechanizmu buforowania obrazu, a obiekt modelu powinien pobierać z tej pamięci podręcznej.
Rob
1
Tak, dokładnie o co mi chodzi. Patrząc na żądanie (które jest pokazane w całości) asynchronicznie pobiera obraz i umieszcza go bezpośrednio w imageView w komórce. (Tak więc użycie komórki do przechowywania danych, tj. Obrazu). To, co powinien robić, to odwoływać się do obiektu i żądać obrazu z tego obiektu (zawartego w tablicy lub gdzieś). Jeśli obiekt nie ma jeszcze obrazu, powinien zwrócić symbol zastępczy i pobrać obraz. Następnie, gdy obraz zostanie pobrany i gotowy do wyświetlenia, poinformuj tabelę, aby mogła zaktualizować komórkę (jeśli jest widoczna).
Fogmeister
1
To, co robi, wymusi pobranie za każdym razem, gdy przewinie do tej komórki w tabeli. Od niego zależy, czy obrazy będą przechowywane w sposób trwały, ale przynajmniej przechowuj je przez cały okres użytkowania widoku tabeli.
Fogmeister
1
Dokładnie: D W ten sposób wystarczy tylko raz pobrać obraz z adresu URL. Zobaczysz to na przykładach Facebook Friend Picker. Po uruchomieniu wszystkie awatary są szarymi symbolami zastępczymi. Następnie podczas przewijania wszystkie wypełniają się w miarę przesuwania. Ale kiedy przewiniesz z powrotem do poprzednio pokazanej komórki, natychmiast wyświetli się już pobrany obraz.
Fogmeister

Odpowiedzi:

230

Zakładając, że szukasz szybkiej poprawki taktycznej, musisz upewnić się, że obraz komórki jest zainicjowany, a także, że wiersz komórki jest nadal widoczny, np:

- (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;
}

Powyższy kod rozwiązuje kilka problemów wynikających z ponownego wykorzystania komórki:

  1. Nie inicjalizujesz obrazu komórki przed zainicjowaniem żądania w tle (co oznacza, że ​​ostatni obraz usuniętej z kolejki komórki będzie nadal widoczny podczas pobierania nowego obrazu). Upewnij się, nilże imagewłaściwości wszelkich widoków obrazów, bo inaczej zobaczysz migotanie obrazów.

  2. Bardziej subtelnym problemem jest to, że w naprawdę wolnej sieci żądanie asynchroniczne może nie zakończyć się, zanim komórka zostanie przewinięta poza ekran. Możesz użyć tej UITableViewmetody cellForRowAtIndexPath:(nie mylić z UITableViewDataSourcemetodą o podobnej nazwie tableView:cellForRowAtIndexPath:), aby sprawdzić, czy komórka dla tego wiersza jest nadal widoczna. Ta metoda zwróci, niljeśli komórka nie jest widoczna.

    Problem polega na tym, że komórka została przewinięta do czasu zakończenia metody asynchronicznej, a co gorsza, komórka została ponownie wykorzystana w innym wierszu tabeli. Sprawdzając, czy wiersz jest nadal widoczny, upewnisz się, że przypadkowo nie zaktualizujesz obrazu obrazem z wiersza, który od tego czasu przewinął się z ekranu.

  3. Nieco niezwiązane z obecnym pytaniem, nadal czułem się zmuszony do zaktualizowania tego, aby wykorzystać nowoczesne konwencje i API, w szczególności:

    • Używaj NSURLSessionzamiast wysyłania -[NSData contentsOfURL:]do kolejki w tle;

    • Użyj dequeueReusableCellWithIdentifier:forIndexPath:zamiast dequeueReusableCellWithIdentifier:(ale upewnij się, że używasz prototypu komórki lub klasy rejestru lub NIB dla tego identyfikatora); i

    • Użyłem nazwy klasy, która jest zgodna z konwencjami nazewnictwa Cocoa (tj. Zaczynaj od dużej litery).

Nawet przy tych poprawkach występują problemy:

  1. Powyższy kod nie buforuje pobranych obrazów. Oznacza to, że jeśli przewiniesz obraz poza ekran iz powrotem na ekran, aplikacja może ponownie spróbować odzyskać obraz. Być może będziesz mieć szczęście wystarczy, że nagłówki odpowiedź Server pozwalają na dość przejrzysty oferowane przez buforowanie NSURLSessioni NSURLCache, ale jeśli nie, będziesz zbędnych żądań serwera i oferując znacznie wolniej UX.

  2. Nie anulujemy żądań dotyczących komórek, które przewijają się poza ekran. Tak więc, jeśli szybko przewiniesz do setnego wiersza, obraz tego wiersza może zostać zaległy za żądaniami z poprzednich 99 wierszy, które nie są już nawet widoczne. Zawsze chcesz mieć pewność, że priorytetyzują żądania dotyczące widocznych komórek, aby uzyskać najlepszy UX.

Najprostszą poprawką, która rozwiązuje te problemy, jest użycie UIImageViewkategorii, takiej jak dostarczona z SDWebImage lub AFNetworking . Jeśli chcesz, możesz napisać własny kod, aby poradzić sobie z powyższymi problemami, ale to dużo pracy, a powyższe UIImageViewkategorie już to za Ciebie zrobiły.

Obrabować
źródło
1
Dzięki. Uważam, że musisz zmienić swoją odpowiedź. updateCell.poster.image = nilto cell.poster.image = nil;updateCell jest wywoływana przed zadeklarowaniem.
Segev
1
Moja aplikacja używa dużo json, więc AFNetworkingjest to zdecydowanie najlepsza droga. Wiedziałem o tym, ale byłem zbyt leniwy, żeby go używać. Po prostu podziwiam, jak działa buforowanie z ich prostą linią kodu. [imageView setImageWithURL:<#(NSURL *)#> placeholderImage:<#(UIImage *)#>];
Segev
2
Wypróbowałem wszystkie powyższe i SDWebImage (faktycznie zatrzymałem się tutaj i nawet nie musiałem próbować AFNetworking) i był to zdecydowanie najlepszy wybór. Dzięki @Rob.
mondousage
1
Po zakończeniu ładowania obrazu i zaktualizowaniu widoku obrazu usuń wskaźnik aktywności. Jedyną sztuczką jest to, że musisz przewidzieć, co się stanie, jeśli komórka zniknie z widoku podczas pobierania obrazu, a komórka zostanie ponownie wykorzystana w innym wierszu, musisz wykryć obecność dowolnego istniejącego wskaźnika aktywności i usunąć / zaktualizować nie wystarczy założyć, że komórka nie ma istniejącego wskaźnika.
Rob
1
Pierwotne pytanie brzmiało „dlaczego powoduje to cellForRowAtIndexPathmigotanie obrazów, gdy przewijam szybko” i wyjaśniłem, dlaczego tak się stało, a także jak to naprawić. Ale potem wyjaśniłem, dlaczego nawet to jest niewystarczające, opisałem kilka głębszych problemów i spierałem się, dlaczego lepiej byłoby skorzystać z jednej z tych bibliotek, aby poradzić sobie z tym bardziej wdzięcznie (nadaj priorytet żądaniom widocznych komórek, buforowanie, aby uniknąć nadmiarowej sieci wnioski itp.). Nie wiem, czego jeszcze oczekiwałeś w odpowiedzi na pytanie „jak zatrzymać migotanie obrazów w widoku tabeli”.
Rob
15

/ * Zrobiłem to w ten sposób, a także przetestowałem * /

Krok 1 = Zarejestruj niestandardową klasę komórki (w przypadku komórki prototypowej w tabeli) lub końcówkę (w przypadku niestandardowej końcówki dla niestandardowej komórki) dla tabeli takiej jak ta w metodzie viewDidLoad:

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

LUB

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

Krok 2 = Użyj metody „dequeueReusableCellWithIdentifier: forIndexPath:” UITableView w ten sposób (w tym celu musisz zarejestrować klasę lub końcówkę):

   - (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
źródło
1
Czy wywołanie cellForRowAtIndexPath w ostatnim bloku nie powoduje ponownego uruchomienia całej sprawy?
Mark Bridges
@MarkBridges, Nie. Właściwie wywołuję tutaj metodę cellForRowAtIndexPath tableView. Nie daj się pomylić z metodą źródła danych tableView o tej samej nazwie. Wymagane, można go nazwać jak [self tableView: tableView cellForRowAtIndexPath: indexPath]; Mam nadzieję, że to rozwiąże Twoje zamieszanie.
Nitesh Borad
14

Istnieje wiele platform, które rozwiązują ten problem. Żeby wymienić tylko kilka:

Szybki:

Cel C:

kean
źródło
Dodaj swoje sugestie, jeśli są inne ramy, które warto rozważyć.
kean
3
Właściwie SDWebImagenie rozwiązuje tego problemu. Możesz kontrolować, kiedy obraz jest pobierany, ale SDWebImageprzypisuj obraz do UIImageViewbez pytania o pozwolenie na zrobienie tego. Zasadniczo problem z pytania nadal nie jest rozwiązany z tą biblioteką.
Bartłomiej Semańczyk
Problem z pytaniem polegał na tym, że autor nie sprawdzał, czy komórka została ponownie wykorzystana, czy nie. Jest to bardzo podstawowy problem, który jest rozwiązywany przez te frameworki, w tym SDWebImage.
kean
SDWebImage jest bardzo opóźnione od czasu iOS 8, był to jeden z moich ulubionych frameworków, ale teraz zaczynam używać PinRemoteImage, który działa naprawdę dobrze.
Joan Cardona
@ BartłomiejSemańczyk Masz rację, ten problem nie został rozwiązany przez SDWebimage
Jan
9

Szybki 3

Piszę własną lekką implementację dla programu ładującego obrazy przy użyciu NSCache. Brak migotania obrazu komórki!

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()
        }
    }
}

Przykład użycia

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
źródło
3
chciałbym ci podziękować. ale kod ma mały problem. if let data = try? Data (contentOf: url) {// proszę zamienić adres URL na lokalizację. pomogłoby to wielu ludziom.
Carl Hung
2
Z kodem takim, jaki jest, pobierasz dwukrotnie plik przez sieć: raz w downloadTaks, raz z danymi (cntentsOf :). Musisz podać lokalizację użytkownika zamiast adresu URL, ponieważ zadanie pobierania po prostu pobiera dane przez sieć i zapisuje dane do pliku tymczasowego i przekazuje ci localUrl (lokalizację w twoim przypadku). Dlatego dane muszą wskazywać lokalny adres URL, aby odczytywać tylko z pliku.
Stéphane de Luca
Czy w przykładzie użycia ma to być „ImageCacheLoader.obtainImageWithPath (imagePath: viewModel.image) .......”?
Tim Kruger
nie będzie działać przy bardzo szybkim przewijaniu, obrazy będą się wielokrotnie zamieniać z powodu ponownego wykorzystania komórki.
Juan Boero,
5

Oto wersja szybka (przy użyciu kodu C @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
źródło
3

Najlepsza odpowiedź nie jest poprawna :(. W rzeczywistości powiązałeś indexPath z modelem, co nie zawsze jest dobre. Wyobraź sobie, że podczas ładowania obrazu dodano kilka wierszy. Teraz komórka dla danego indexPath istnieje na ekranie, ale obraz nie jest już poprawna! Sytuacja jest mało prawdopodobna i trudna do odtworzenia, ale jest możliwa.

Lepiej jest użyć podejścia MVVM, powiązać komórkę z viewModel w kontrolerze i załadować obraz w viewModel (przypisując sygnał ReactiveCocoa metodą switchToLatest), a następnie zasubskrybować ten sygnał i przypisać obraz do komórki! ;)

Musisz pamiętać, aby nie nadużywać MVVM. Widoki muszą być bardzo proste! Natomiast ViewModels powinny być wielokrotnego użytku! Dlatego bardzo ważne jest powiązanie widoku (UITableViewCell) i ViewModel w kontrolerze.

badeleux
źródło
1
Tak, moja ścieżka indeksu „poprawka taktyczna” (której nie polecałem, a raczej była to tylko najskromniejsza edycja w celu rozwiązania problemu OP) cierpi na ten problem (ale tylko wtedy, gdy w widoku tabeli nadal są dodawane / usuwane wiersze). A jeśli to zjawisko się ujawni, mógłbym to naprawić na inne sposoby (zamiast szukać przy użyciu tej samej ścieżki indeksu, po prostu model zapytania dla odpowiedniego wiersza). Ale ta taktyczna poprawka ma jeszcze bardziej rażące problemy (które opisuję powyżej) niż ta, którą tutaj poruszysz. Jeśli korzystasz z UIImageViewrozwiązania kategorii, które radzę, nie ma takiego problemu dotyczącego ścieżek indeksowania.
Rob
2
Mogę zabrzmieć trochę pedantycznie, ale przywołanie jakiejkolwiek logiki z VIEW jest nadużyciem tej architektury.
badeleux
3

W moim przypadku nie było to spowodowane buforowaniem obrazu (używane SDWebImage). Było to spowodowane niezgodnością znacznika niestandardowej komórki z indexPath.row.

Na cellForRowAtIndexPath:

1) Przypisz wartość indeksu do komórki niestandardowej. Na przykład,

cell.tag = indexPath.row

2) W głównym wątku, przed przypisaniem obrazu, sprawdź, czy obraz należy do odpowiedniej komórki, dopasowując go do tagu.

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

Dziękuję "Rob" .... Miałem ten sam problem z UICollectionView i twoja odpowiedź pomoże mi rozwiązać mój problem. Oto mój kod:

 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
źródło
Dla mnie mycell *updateCell = (id)[collectionView cellForItemAtIndexPath:indexPath];nigdy nie jest zero, więc nie ma to żadnego efektu.
karbokacja
1
możesz sprawdzić, czy Twoja komórka jest widoczna, czy nie, przez: for (mycell * updateCell w collectionView.visibleCells) {cellVisible = YES; } if (cellVisible) {cell.coverImageView.imageURL = [NSURL URLWithString: [Dict valueForKey: @ "ImageURL"]]; } U mnie też to działa
sneha
@sneha Tak, możesz sprawdzić, czy jest widoczny, wykonując iterację w visibleCellsten sposób, ale podejrzewam, że używanie [collectionView cellForItemAtIndexPath:indexPath]jest bardziej wydajne (i właśnie dlatego wykonujesz to wywołanie).
Rob
@sneha Nawiasem mówiąc, w przykładowym kodzie w tej odpowiedzi powyżej sprawdzasz, czy updateCellnie nil, ale potem go nie używasz. Powinieneś użyć go nie tylko do określenia, czy komórka widoku kolekcji jest nadal widoczna, ale powinieneś użyć updateCelltego wewnątrz tego bloku, nie cell(co może już nie być poprawne). I oczywiście, jeśli tak nil, nie musisz nic robić (ponieważ ta komórka nie jest widoczna).
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
źródło
0

Myślę, że chcesz przyspieszyć ładowanie komórki w czasie ładowania obrazu komórki w tle. W tym celu wykonaliśmy następujące kroki:

  1. Sprawdzanie, czy plik istnieje w katalogu dokumentów, czy nie.

  2. Jeśli nie, załaduj obraz po raz pierwszy i zapisz go w naszym katalogu dokumentów telefonu. Jeśli nie chcesz zapisywać obrazu w telefonie, możesz załadować obrazy komórek bezpośrednio w tle.

  3. Teraz proces ładowania:

Wystarczy dołączyć: #import "ManabImageOperations.h"

Kod wygląda jak poniżej dla komórki:

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

Sprawdź odpowiedź i skomentuj, jeśli wystąpi jakikolwiek problem ....

Manab Kumar Mal
źródło
0

Po prostu zmień,

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

W

    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
źródło
0

Możesz po prostu podać swój adres 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];
Użytkownik558
źródło
czy mogę poznać powód?
Użytkownik558
-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
źródło
2
Zrzuty kodu bez żadnego wyjaśnienia rzadko są pomocne. Rozważ edycję tej odpowiedzi, aby podać kontekst.
Chris