Wenn Sie Zeilen vorab abrufen möchten, können Sie auf die Methoden UIScrollViewDelegate
reagieren, um zu bestimmen, wann das Tabellenscrolling ausgeführt wird, wodurch ein Prefetch der Zeilen ausgelöst wird. Sie können die Prefetch mit SDWebImagePrefetcher
durchführen (in meiner ursprünglichen Antwort, die ich ein wenig abweisend dieser nützlichen Klasse war, aber es scheint relativ gut an die Arbeit):
- (void)viewDidLoad
{
[super viewDidLoad];
// the details don't really matter here, but the idea is to fetch data,
// call `reloadData`, and then prefetch the other images
NSURL *url = [NSURL URLWithString:kUrlWithJSONData];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (connectionError) {
NSLog(@"sendAsynchronousRequest error: %@", connectionError);
return;
}
self.objects = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
[self.tableView reloadData];
[self prefetchImagesForTableView:self.tableView];
}];
}
// some of the basic `UITableViewDataDelegate` methods have been omitted because they're not really relevant
Hier ist die einfache cellForRowAtIndexPath
(nicht ganz relevant, sondern nur zeigen, dass, wenn Sie SDWebImagePrefetcher
verwenden, müssen Sie nicht zu verwirren haben um mit cellForRowAtIndexPath
:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = @"Cell";
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
NSAssert([cell isKindOfClass:[CustomCell class]], @"cell should be CustomCell");
[cell.customImageView setImageWithURL:[self urlForIndexPath:indexPath] placeholderImage:nil];
[cell.customLabel setText:[self textForIndexPath:indexPath]];
return cell;
}
Diese UIScrollViewDelegate
Methoden prefetch mehr Zeilen, wenn Oberflächen
Scrollen
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
// if `decelerate` was true for `scrollViewDidEndDragging:willDecelerate:`
// this will be called when the deceleration is done
[self prefetchImagesForTableView:self.tableView];
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
// if `decelerate` is true, then we shouldn't start prefetching yet, because
// `cellForRowAtIndexPath` will be hard at work returning cells for the currently visible
// cells.
if (!decelerate)
[self prefetchImagesForTableView:self.tableView];
}
Sie müssen natürlich eine Vorabrufroutine implementieren. Dies ruft die NSIndexPath
Werte für die Zellen auf jeder Seite der sichtbaren Zellen ab, ruft ihre Bild-URLs ab und ruft dann diese Daten vorab ab.
/** Prefetch a certain number of images for rows prior to and subsequent to the currently visible cells
*
* @param tableView The tableview for which we're going to prefetch images.
*/
- (void)prefetchImagesForTableView:(UITableView *)tableView
{
NSArray *indexPaths = [self.tableView indexPathsForVisibleRows];
if ([indexPaths count] == 0) return;
NSIndexPath *minimumIndexPath = indexPaths[0];
NSIndexPath *maximumIndexPath = [indexPaths lastObject];
// they should be sorted already, but if not, update min and max accordingly
for (NSIndexPath *indexPath in indexPaths)
{
if (indexPath.section < minimumIndexPath.section || (indexPath.section == minimumIndexPath.section && indexPath.row < minimumIndexPath.row)) minimumIndexPath = indexPath;
if (indexPath.section > maximumIndexPath.section || (indexPath.section == maximumIndexPath.section && indexPath.row > maximumIndexPath.row)) maximumIndexPath = indexPath;
}
// build array of imageURLs for cells to prefetch
NSMutableArray *imageURLs = [NSMutableArray array];
indexPaths = [self tableView:tableView priorIndexPathCount:kPrefetchRowCount fromIndexPath:minimumIndexPath];
for (NSIndexPath *indexPath in indexPaths)
[imageURLs addObject:[self urlForIndexPath:indexPath]];
indexPaths = [self tableView:tableView nextIndexPathCount:kPrefetchRowCount fromIndexPath:maximumIndexPath];
for (NSIndexPath *indexPath in indexPaths)
[imageURLs addObject:[self urlForIndexPath:indexPath]];
// now prefetch
if ([imageURLs count] > 0)
{
[[SDWebImagePrefetcher sharedImagePrefetcher] prefetchURLs:imageURLs];
}
}
Dies sind die Utility-Methoden für das Erhalten der NSIndexPath
für die Zeilen sofort die sichtbaren Zellen vorhergehenden sowie diese unmittelbar nach den sichtbaren Zellen:
/** Retrieve NSIndexPath for a certain number of rows preceding particular NSIndexPath in the table view.
*
* @param tableView The tableview for which we're going to retrieve indexPaths.
* @param count The number of rows to retrieve
* @param indexPath The indexPath where we're going to start (presumably the first visible indexPath)
*
* @return An array of indexPaths.
*/
- (NSArray *)tableView:(UITableView *)tableView priorIndexPathCount:(NSInteger)count fromIndexPath:(NSIndexPath *)indexPath
{
NSMutableArray *indexPaths = [NSMutableArray array];
NSInteger row = indexPath.row;
NSInteger section = indexPath.section;
for (NSInteger i = 0; i < count; i++) {
if (row == 0) {
if (section == 0) {
return indexPaths;
} else {
section--;
row = [tableView numberOfRowsInSection:section] - 1;
}
} else {
row--;
}
[indexPaths addObject:[NSIndexPath indexPathForRow:row inSection:section]];
}
return indexPaths;
}
/** Retrieve NSIndexPath for a certain number of following particular NSIndexPath in the table view.
*
* @param tableView The tableview for which we're going to retrieve indexPaths.
* @param count The number of rows to retrieve
* @param indexPath The indexPath where we're going to start (presumably the last visible indexPath)
*
* @return An array of indexPaths.
*/
- (NSArray *)tableView:(UITableView *)tableView nextIndexPathCount:(NSInteger)count fromIndexPath:(NSIndexPath *)indexPath
{
NSMutableArray *indexPaths = [NSMutableArray array];
NSInteger row = indexPath.row;
NSInteger section = indexPath.section;
NSInteger rowCountForSection = [tableView numberOfRowsInSection:section];
for (NSInteger i = 0; i < count; i++) {
row++;
if (row == rowCountForSection) {
row = 0;
section++;
if (section == [tableView numberOfSections]) {
return indexPaths;
}
rowCountForSection = [tableView numberOfRowsInSection:section];
}
[indexPaths addObject:[NSIndexPath indexPathForRow:row inSection:section]];
}
return indexPaths;
}
Es gibt es eine Menge, aber in Wirklichkeit, SDWebImage
und seine SDWebImagePrefetcher
macht das schwere Heben.
Ich füge meine ursprüngliche Antwort aus Gründen der Vollständigkeit hier ein.
Ursprüngliche Antwort:
Wenn Sie etwas Prefetching mit SDWebImage
tun mögen, könnten Sie so etwas wie die folgenden tun:
einen Abschluss Block zu Ihrem setImageWithURL
Anruf hinzufügen:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(@"%s", __FUNCTION__);
static NSString *cellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
TableModelRow *rowData = self.objects[indexPath.row];
cell.textLabel.text = rowData.title;
[cell.imageView setImageWithURL:rowData.url
placeholderImage:[UIImage imageNamed:@"placeholder.png"]
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType) {
[self prefetchImagesForTableView:tableView];
}];
return cell;
}
Ich muss gestehen, ich mag nicht wirklich anrufen meine prefetcher
Routine hier (ich wünschte, iOS hätte einige nette didFinishTableRefresh
Delegate-Methode), aber es funktioniert, auch wenn es die Routine öfter anruft, als ich wirklich wollen würde. Ich stelle nur sicher, dass die Routine unten sicherstellt, dass keine redundanten Anfragen gestellt werden.
Wie dem auch sei, ich schreibe eine Prefetch-Routine, die für, sagen wir, in den nächsten zehn Bilder aussieht:
const NSInteger kPrefetchRowCount = 10;
- (void)prefetchImagesForTableView:(UITableView *)tableView
{
// determine the minimum and maximum visible rows
NSArray *indexPathsForVisibleRows = [tableView indexPathsForVisibleRows];
NSInteger minimumVisibleRow = [indexPathsForVisibleRows[0] row];
NSInteger maximumVisibleRow = [indexPathsForVisibleRows[0] row];
for (NSIndexPath *indexPath in indexPathsForVisibleRows)
{
if (indexPath.row < minimumVisibleRow) minimumVisibleRow = indexPath.row;
if (indexPath.row > maximumVisibleRow) maximumVisibleRow = indexPath.row;
}
// now iterate through our model;
// `self.objects` is an array of `TableModelRow` objects, one object
// for every row of the table.
[self.objects enumerateObjectsUsingBlock:^(TableModelRow *obj, NSUInteger idx, BOOL *stop) {
NSAssert([obj isKindOfClass:[TableModelRow class]], @"Expected TableModelRow object");
// if the index is within `kPrefetchRowCount` rows of our visible rows, let's
// fetch the image, if it hasn't already done so.
if ((idx < minimumVisibleRow && idx >= (minimumVisibleRow - kPrefetchRowCount)) ||
(idx > maximumVisibleRow && idx <= (maximumVisibleRow + kPrefetchRowCount)))
{
// my model object has method for initiating a download if needed
[obj downloadImageIfNeeded];
}
}];
}
In der Download-Routine, können Sie überprüfen, ob der Download Bild hat begonnen, und wenn nicht, dann starte es. Um dies zu tun mit SDWebImage
, halte ich einen weak
Zeiger auf den Web-Bild Betrieb in meiner TableModelRow
Klasse (die Modellklasse, die die einzelnen Reihen von meinem Tisch Rücken):
@property (nonatomic, weak) id<SDWebImageOperation> webImageOperation;
Ich habe dann den downloadImageIfNeeded
Routine beginnt einen Download wenn es nicht schon ist (Sie können sehen, warum die Erstellung dieser weak
so wichtig war ... Ich überprüfe, ob in dieser Zeile bereits ein Vorgang aussteht, bevor ein anderer gestartet wird). Ich mache nichts mit dem heruntergeladenen Bild (kurz, für Debugging-Zwecke, Protokollierung der Tatsache, dass ein Download durchgeführt wurde), sondern einfach nur herunterladen und lassen SDImageWeb
verfolgen das zwischengespeicherte Bild für mich, also wenn cellForRowAtIndexPath
später anfordert Bild, wenn der Benutzer scrollt, ist es da, bereit und wartet.
- (void)downloadImageIfNeeded
{
if (self.webImageOperation)
return;
SDWebImageManager *imageManager = [SDWebImageManager sharedManager];
self.webImageOperation = [imageManager downloadWithURL:self.url
options:0
progress:nil
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished) {
NSLog(@"%s: downloaded %@", __FUNCTION__, self.title);
// I'm not going to do anything with the image, but `SDWebImage` has now cached it for me
}];
}
Ein Teil von mir denkt, es könnte robuster sein imageManager.imageCache
Instanz Methode aufrufen queryDiskCacheForKey
zuerst, aber nach ein paar Tests zu tun, es sieht nicht so aus, dass benötigt wird (und die downloadWithURL
macht das für uns jedenfalls).
Ich möchte darauf hinweisen, dass die SDImageWeb
Bibliothek hat eine SDWebImagePrefetcher
Klasse (siehe the documentation). Der Name der Klasse ist unglaublich vielversprechend, aber wenn ich mir den Code mit aller Ehrerbietung gegenüber einer ansonsten exzellenten Bibliothek anschaue, fühlt sich das für mich nicht sehr robust an (zB ist es eine einfache Liste von URLs, die man holen kann und wenn man es nochmal macht) , löscht es die vorherige Liste ohne eine Vorstellung von "Hinzufügen zu der Warteschlange" oder ähnlichem. Es ist eine vielversprechende Vorstellung, aber ein wenig schwach in der Ausführung. Und als ich es versuchte, litt meine UX merklich.
Also, ich bin geneigt, SDWebImagePrefetcher
(bis es zumindest verbessert wird) nicht zu verwenden, und bleiben Sie bei meiner rudimentären Prefetch-Technik. Es ist nicht besonders anspruchsvoll, aber es scheint zu funktionieren.
Sind Sie sicher, dass diese Zellen schaffen, die offscreen sind? Kennen Sie das Verhalten von UITableView? – Exploring
Die Zellen werden wiederverwendet. – user2082760
Ja, welche Zellen sind dann offline, dann wird die Instanzmethode für diese Zellen aufgerufen? Es ist nicht das Problem von SDWebImage. – Exploring