Повторное использование UICollectionViewCells во время прокрутки

У меня проблема,

У меня есть простой UICollectionView со статическими 200 ячейками, которые загружают изображения из Flickr.

мой CellForItemAtIndexPath выглядит так:

- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    UICollectionViewCell *cell = [cv dequeueReusableCellWithReuseIdentifier:@"FlickrCell" forIndexPath:indexPath];
    cell.backgroundColor = [self generateRandomUIColor];

    if(![[cell.subviews objectAtIndex:0] isKindOfClass:[PFImageView class]])
    {
        NSURL *staticPhotoURL = [self.context photoSourceURLFromDictionary:[self.photos objectAtIndex:indexPath.row] size:OFFlickrSmallSize];
        PFImageView *imageView = [[PFImageView alloc] initWithFrame:CGRectMake(0, 0, cell.frame.size.height, cell.frame.size.width) andImageURL:staticPhotoURL andOwningCell:cell];
        [cell addSubview:imageView];
    }
    return cell;
}

PFImageView является подклассом UIImageView который загружает URL-адрес фотографии Flickr в фоновый поток, а затем обновляет свое собственное изображение в основном потоке - это работает нормально.

Логика действительно проста - я создаю ячейку, если ее нет.

Если ячейка (которую я ожидаю снять с очереди и у которой уже есть PFImageView) не имеет PFImageView, я выделяю и инициирую imageView для ячейки и добавляю его как подпредставление ячейки.

Таким образом, я ожидаю, что если ячейка была удалена из очереди, она уже должна иметь PFImageView в качестве подпредставления, и, поскольку мы не должны входить в оператор if для создания нового imageView и запуска нового запроса на загрузку фотографий

Вместо этого я вижу, что ячейки вверху и внизу UICollectionView на мгновение "уходят с экрана" - когда они возвращаются на экран, они не используются повторно, и, по-видимому, создается новая ячейка и изображение обновляется.

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

2) Почему клетки не используются повторно?

Большое спасибо за ваше время.

Джон

4 ответа

Решение

Я вижу несколько потенциальных проблем:

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

  2. Я не вижу, что вы вызываете registerClass: forCellWithReuseIdentifier: или registerNib:forCellWithReuseIdentifier:, один из которых требуется для правильного использования очереди ( https://developer.apple.com/library/ios/documentation/uikit/reference/UICollectionViewCell_class/Reference/Reference.html).

  3. Вы устанавливаете URL-адрес PFImageView только в том случае, если вам необходимо создать PFImageView. Идея снятия с повторного использования представлений заключается в том, что вы создадите лишь небольшое подмножество необходимых представлений, а UITableView будет перерабатывать их при перемещении за пределы экрана. Вам необходимо сбросить значение запрашиваемого indexPath, даже если вы не создаете новый контент.

Если ваш случай так же прост, как вы описали, вы, вероятно, можете избежать добавления PFImageView к свойству contentView вашего UICollectionView в устаревшем состоянии.

В вашем контроллере:

// solve problem 2
[self.collectionView registerClass:[UICollectionViewCell class] forReuseIdentifer:@"FlickrCell"];

В коллекции ViewView:cellForItemAtIndexPath

UICollectionViewCell *cell = [cv dequeueReusableCellWithReuseIdentifier:@"FlickrCell" forIndexPath:indexPath];
cell.backgroundColor = [self generateRandomUIColor];

// solve problem 1 by looking in the contentView for your subview (and looping instead of assuming at 0)
PFImageView *pfImageView = nil;
for (UIView *subview in cell.contentView.subviews)
{
    if ([subview isKindOfClass:[PFImageView class]])
    {
        pfImageView = (PFImageView *)subview;
        break;
    }
}

NSURL *staticPhotoURL = [self.context photoSourceURLFromDictionary:[self.photos objectAtIndex:indexPath.row] size:OFFlickrSmallSize];    
if (pfImageView == nil)
{
    // No PFImageView, create one
    // note the use of contentView!
    pfImageView = [[PFImageView alloc] initWithFrame:CGRectMake(0, 0, cell.contentView.frame.size.height, cell.frame.size.width) andImageURL:staticPhotoURL andOwningCell:cell.contentView];
    [cell.contentView addSubview:pfImageView];
}
else
{
    // Already have recycled view.
    // need to reset the url for the pfImageView. (Problem 3)
    // not sure what PFImageView looks like so this is an e.g. I'd probably remove the
    // URL loading from the ctr above and instead have a function that loads the
    // image. Then, you could do this outside of the if, regardless of whether you had 
    // to alloc the child view or not.
    [pfImageView loadImageWithUrl:staticPhotoURL];
    // if you really only have 200 static images, you might consider caching all of them
}
return cell;

Для менее простых случаев (например, когда я хочу визуально расположить ячейку или когда у меня есть несколько дочерних элементов в контенте), я обычно настраиваю свой UICollectionViewCell, используя Interface Builder.

  1. Создайте подкласс UICollectionViewCell в проекте (в вашем случае назовите его PFImageCell).
  2. Добавьте свойство IBOutlet к этому подклассу для представления, которое я хочу изменить при инициализации (в вашем случае, UIImageView).@property (nonatomic, assign) IBOutlet UIImageView *image;
  3. В Интерфейсном Разработчике создайте прототипную ячейку для UITableView.
  4. В таблице свойств для этой ячейки прототипа определите подкласс UICollectionViewCell как класс.
  5. Дайте ячейке прототипа идентификатор (идентификатор повторного использования) в листе свойств.
  6. Добавьте дочерний элемент view в конструкторе интерфейса в ячейку прототипа (здесь, UIImageView).
  7. Используйте IB для сопоставления свойства IBOutlet с добавленным UIImageView
  8. Затем, по очереди в cellForRowAtIndexPath, приведите результат в исключенном состоянии к подклассу (PFImageCell) и установите значение экземпляра свойства IBOutlet. Здесь вы должны загрузить правильное изображение для вашего UIImageView.

UICollectionView будет использовать ячейки для максимальной эффективности. Это не гарантирует какой-либо конкретной стратегии повторного использования или популяций. Как ни странно, он, кажется, размещает и удаляет ячейки на основе целочисленной мощности двух областей - например, на iPad без сетчатки он может разделить вашу область прокрутки на области 1024x1024, а затем заполнить и депопулировать каждую из этих областей, когда они переходят в и из видимой области. Однако вы не должны прогнозировать какие-либо ожидания относительно его точного поведения.

Кроме того, использование ячеек представления коллекции неверно. Смотрите документацию. Ячейка явно имеет по крайней мере два подпредставления - backgroundView а также contentView, Таким образом, если вы добавите подпредставление, оно будет по крайней мере с индексом 2 и, в действительности, индекс будет неопределенным. В любом случае вы должны добавить подпредставления к contentView, а не в самой клетке.

Самый обычный способ сделать то, что вы делаете, это создать UICollectionView подкласс, который по своей природе имеет PFImageView внутри.

Я не уверен, используется ли клетка повторно или нет. Возможно, он используется повторно, но его нет. Моим предложением было бы создать класс PFImageViewCollectionViewCell (подкласс UICollectionViewCell) и зарегистрировать его в качестве ячейки CollectionView и попробовать. Это то, что я делаю и буду делать, если мне нужно подпредставление внутри клетки.

Попробуйте добавить тег на этот конкретный UIImageView

- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath {

    static int photoViewTag = 54353532;

    UICollectionViewCell *cell = [cv dequeueReusableCellWithReuseIdentifier:@"FlickrCell" forIndexPath:indexPath];
    cell.backgroundColor = [self generateRandomUIColor];

    PFImageView *photoView = [cell.contentView viewWithTag:photoViewTag];

    // Create a view
    //
    if (!photoView) {
        photoView = [[PFImageView alloc] initWithFrame:CGRectMake(0, 0, cell.frame.size.height, cell.frame.size.width) andImageURL:staticPhotoURL andOwningCell:cell];
        imageView.tag = photoViewTag;
        [cell.contentView addSubview:imageView];
    }

    // Update the current view
    //
    else {
        NSURL *staticPhotoURL = [self.context photoSourceURLFromDictionary:[self.photos objectAtIndex:indexPath.row] size:OFFlickrSmallSize];
        photoView.imageURL = staticPhotoURL;
    }
    return cell;
}

Я очень рекомендую создать свой собственный UICollectionViewCell подкласс хотя.

РЕДАКТИРОВАТЬ: Кроме того, обратите внимание, что я использовал contentView свойство вместо добавления его непосредственно в ячейку.

Другие вопросы по тегам