загрузка изображения на неправильный UITableViewCell при прокрутке - PullRequest
3 голосов
/ 03 декабря 2011

Я загружаю набор изображений для определенной ячейки, и он работает нормально, только если я жду, пока все изображения загружены, я использую нить для извлечения изображений из Интернета, проблема заключается в том, что при прокрутке табличного представления во время загрузкипроцесс все еще продолжается, он сгенерирует изображение в неправильном положении ячейки.Я знаю, что это неправильная позиция, потому что я также установил метку для каждого набора изображений.Любая помощь будет признательна, спасибо заранее !!!!

вот пример кода.

- (void)viewDidLoad{
[super viewDidLoad];
/* loads xml from the web and parses the images and store it in core data(sqlite)
   it uses NSFetchedController protocol for inserting cell after saving into core data
*/
[self loadFromXmlSource];
}

У меня есть 3 раздела, которые используют 3 разных прототипа ячейки в раскадровке, которую я имеюпроблема в разделе фотографий.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath         *)indexPath
{
Media *mediaModel = [_fetchedResultsController objectAtIndexPath:indexPath];
NSString *CellIdentifier = @"MediaCellNoImage";
if ([[mediaModel.category lowercaseString] isEqualToString:@"photos"]) {
    CellIdentifier = @"MediaGalleryCell";
} else if ([[mediaModel.category lowercaseString] isEqualToString:@"videos"]) {
    CellIdentifier = @"MediaCellNoImage";
} else if ([[mediaModel.category lowercaseString] isEqualToString:@"podcasts"]) {
    CellIdentifier = @"MediaCell";
}

MediaGalleryCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
    cell = [[MediaGalleryCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// I am also having a problem with image duplication so I always delete all images
if([cell.reuseIdentifier isEqualToString:@"MediaGalleryCell"]) {
    for(id subview in cell.scrollView.subviews){
        [subview removeFromSuperview];
    }
}

[self configureCell:cell atIndexPath:indexPath];
return cell;
}

здесь находится ячейка конфигурации, в которой я создаю поток для загрузки изображений

-(void)configureCell:(MediaGalleryCell *)cell atIndexPath:(NSIndexPath *)indexPath
{

Media *mediaModel = [_fetchedResultsController objectAtIndexPath:indexPath];
if ([[mediaModel.category lowercaseString] isEqualToString:@"photos"]) {
        NSEnumerator *e = [mediaModel.attachments objectEnumerator];
        Attachment *attachment;
        // iterate all images in a particular cell
        while ((attachment = [e nextObject])) {
                NSDictionary *objects = [[NSMutableDictionary alloc] initWithObjectsAndKeys:

                                         attachment.identifier, @"identifier",
                                         attachment.thumb, @"thumb",
                                         attachment.filename, @"filename",
                                         cell, @"cell", 
                                         nil];

                // for each image spawn a thread i am also passing the cell in order to set it there.
                [NSThread detachNewThreadSelector:@selector(loadImageInScrollView:)
                                         toTarget:self withObject:objects];
        }   
} else {
    // code for other section
}


cell.titleLabel.text = mediaModel.title;
cell.contentLabel.text = mediaModel.content;
}

вот метод, который находится в потоке

-(void)loadImageInScrollView:(NSDictionary *)objects
{
MediaGalleryCell *cell = [objects valueForKey:@"cell"];
SBCacheFile *cacheFile = [self getCache];
NSString *finalIdentifier = [NSString stringWithFormat:@"%@_s", [objects valueForKey:@"identifier"]];

// here is where I fetched the image and cache it locally
UIImage *image = [cacheFile getCachedFileWithUrl:[objects valueForKey:@"thumb"]
                                        withName:[objects valueForKey:@"filename"]
                                        andStringIdentifier:finalIdentifier];

NSDictionary *obj = [[NSDictionary alloc] initWithObjectsAndKeys:
                     cell, @"cell",
                     image, @"image",
                     nil];
//after fetching the image I need to send it back to the main thread in order to do some UI operation
[self performSelectorOnMainThread:@selector(setImageViewForGallery:) withObject:obj waitUntilDone:YES];

}

вот последний фрагмент кода, в котором я добавляю изображение как подпредставление свойства ячейки, которое представляет собой scrollView, и воспринимает его как карусель изображений в ячейке.

-(void)setImageViewForGallery:(NSDictionary *)dictionary
{
MediaGalleryCell *cell = [dictionary valueForKey:@"cell"];
UIImage *image = [dictionary valueForKey:@"image"];

UIImageView *imageView = [[UIImageView alloc] initWithImage:image];

UIImageView *lastImageView =[[cell.scrollView subviews] lastObject];
if (lastImageView != nil) {
    [imageView setFrame:CGRectMake((lastImageView.frame.origin.x + lastImageView.frame.size.width + 10.0f), 
                                   5.0f, 70.0f, 70.0f)];
} else {
    [imageView setFrame:CGRectMake(5.0f, 5.0f, 70.0f, 70.0f)];
}

 // This is where I add the imageView
 [cell.scrollView addSubview:imageView];

 UIImageView *lastViewForSize = [[cell.scrollView subviews] lastObject];
    [cell.scrollView setContentSize:CGSizeMake(lastViewForSize.frame.origin.x + lastViewForSize.frame.size.width + 0.5,cell.scrollView.frame.size.height)]; 
}

Ответы [ 2 ]

3 голосов
/ 03 декабря 2011

Во-первых, это невероятно опасно:

// for each image spawn a thread i am also passing the cell in order to set it there.
[NSThread detachNewThreadSelector:@selector(loadImageInScrollView:)
                         toTarget:self withObject:objects];

Это может развить огромное (и неограниченное) количество потоков. Вероятно, что при прокрутке завершаются потоки для ячеек, которые уже были повторно использованы, и поэтому топают их новые данные.

Есть несколько вещей, которые вы должны сделать здесь, чтобы улучшить ситуацию. Во-первых, вы редко хотите использовать detachNewThreadSelector:toTarget:withObject:. Вместо этого используйте NSOperationQueue, очередь GCD или только одного работника NSThread и performSelectorOnThread:...

Далее вы смешали модель и логику просмотра. Ваш текущий способ хранения данных модели (список изображений) внутри видов (ячеек). Это не работает, когда клетки используются повторно. Представления не для хранения данных.

У вас должна быть модель, которая отслеживает все изображения и их местоположение. В вашем случае это, вероятно, просто массив массивов (строк / изображений). Источник данных табличного представления всегда будет возвращать ячейку с текущим содержимым соответствующих строк массива.

Всякий раз, когда модель изменяется (потому что было загружено новое изображение), ваш делегат табличного представления должен вызвать reloadRowsAtIndexPaths:withRowAnimation:, чтобы табличное представление знало, что строка изменилась. Если эта строка окажется необходимой, то (и только тогда) табличное представление запросит у источника данных новую ячейку, которую вы обновите новыми изображениями. Этот подход чище, стабильнее, быстрее и использует меньше системных ресурсов.

1 голос
/ 03 декабря 2011

Это классическая ловушка UITableView . Когда вы прокручиваете ячейку вне поля зрения, она будет использоваться повторно для отображения других данных. В основном он выскакивает вверху, заполняется новыми данными и снова появляется внизу.

Однако ваши фоновые процессы не уведомляются об этом. Поэтому, когда изображение наконец загружается, прикрепите его к ячейке таблицы, которая использовалась для чего-то другого.

Таким образом, чтобы исправить это, вам нужно либо отменить загрузку изображения, если ячейка таблицы больше не видна, либо, по крайней мере, уведомить поток, что ячейка таблицы была повторно использована и что больше невозможно назначить изображение после загрузил. Это может произойти, когда ячейка прокручивается вне поля зрения или когда ячейка используется повторно для отображения других данных.

В моем приложении я использую центральный кеш изображений. Он использует до четырех потоков для загрузки изображений. И он хранит список всех ячеек таблицы, ожидающих загрузки изображения. Если одна из этих ячеек таблицы появляется в другом запросе изображения, предыдущий запрос изменяется, поэтому он не обновляет ячейку таблицы, а просто помещает изображение в центральный кеш.

...