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;
}
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.Odpowiedzi:
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:
Powyższy kod rozwiązuje kilka problemów wynikających z ponownego wykorzystania komórki:
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
żeimage
właściwości wszelkich widoków obrazów, bo inaczej zobaczysz migotanie obrazów.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
UITableView
metodycellForRowAtIndexPath:
(nie mylić zUITableViewDataSource
metodą o podobnej nazwietableView:cellForRowAtIndexPath:
), aby sprawdzić, czy komórka dla tego wiersza jest nadal widoczna. Ta metoda zwróci,nil
jeś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.
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
NSURLSession
zamiast wysyłania-[NSData contentsOfURL:]
do kolejki w tle;Użyj
dequeueReusableCellWithIdentifier:forIndexPath:
zamiastdequeueReusableCellWithIdentifier:
(ale upewnij się, że używasz prototypu komórki lub klasy rejestru lub NIB dla tego identyfikatora); iUż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:
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
NSURLSession
iNSURLCache
, ale jeśli nie, będziesz zbędnych żądań serwera i oferując znacznie wolniej UX.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
UIImageView
kategorii, 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ższeUIImageView
kategorie już to za Ciebie zrobiły.źródło
updateCell.poster.image = nil
tocell.poster.image = nil;
updateCell jest wywoływana przed zadeklarowaniem.AFNetworking
jest 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 *)#>];
cellForRowAtIndexPath
migotanie 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”./ * 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:
LUB
Krok 2 = Użyj metody „dequeueReusableCellWithIdentifier: forIndexPath:” UITableView w ten sposób (w tym celu musisz zarejestrować klasę lub końcówkę):
źródło
Istnieje wiele platform, które rozwiązują ten problem. Żeby wymienić tylko kilka:
Szybki:
Cel C:
źródło
SDWebImage
nie rozwiązuje tego problemu. Możesz kontrolować, kiedy obraz jest pobierany, aleSDWebImage
przypisuj obraz doUIImageView
bez pytania o pozwolenie na zrobienie tego. Zasadniczo problem z pytania nadal nie jest rozwiązany z tą biblioteką.Szybki 3
Piszę własną lekką implementację dla programu ładującego obrazy przy użyciu NSCache. Brak migotania obrazu komórki!
ImageCacheLoader.swift
Przykład użycia
źródło
Oto wersja szybka (przy użyciu kodu C @Nitesh Borad): -
źródło
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.
źródło
UIImageView
rozwiązania kategorii, które radzę, nie ma takiego problemu dotyczącego ścieżek indeksowania.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,
2) W głównym wątku, przed przypisaniem obrazu, sprawdź, czy obraz należy do odpowiedniej komórki, dopasowując go do tagu.
źródło
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:
źródło
mycell *updateCell = (id)[collectionView cellForItemAtIndexPath:indexPath];
nigdy nie jest zero, więc nie ma to żadnego efektu.visibleCells
ten sposób, ale podejrzewam, że używanie[collectionView cellForItemAtIndexPath:indexPath]
jest bardziej wydajne (i właśnie dlatego wykonujesz to wywołanie).updateCell
nienil
, 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ćupdateCell
tego wewnątrz tego bloku, niecell
(co może już nie być poprawne). I oczywiście, jeśli taknil
, nie musisz nic robić (ponieważ ta komórka nie jest widoczna).źródło
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:
Sprawdzanie, czy plik istnieje w katalogu dokumentów, czy nie.
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.
Teraz proces ładowania:
Wystarczy dołączyć:
#import "ManabImageOperations.h"
Kod wygląda jak poniżej dla komórki:
ManabImageOperations.h:
ManabImageOperations.m:
Sprawdź odpowiedź i skomentuj, jeśli wystąpi jakikolwiek problem ....
źródło
Po prostu zmień,
W
źródło
Możesz po prostu podać swój adres URL,
źródło
źródło