UICollectionView: как получить размер элемента для определенного элемента после его настройки в методе делегирования

Я возился с новыми классами UICollectionView и UICollectionViewLayout. Я создал собственный макет, подклассифицируя UICollectionViewFlowLayout.

Мои размеры ячеек меняются динамически, и я устанавливаю размеры элементов с помощью метода делегата ниже

- (CGSize)collectionView:(UICollectionView *)collectionView
                  layout:(UICollectionViewLayout*)collectionViewLayout
  sizeForItemAtIndexPath:(NSIndexPath *)indexPath {

      NSLog(@"SETTING SIZE FOR ITEM AT INDEX %d", indexPath.row);
      return CGSizeMake(80, 80);
}

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

Однако я не могу найти какое-либо свойство в UICollectionView или UICollectionViewFlowLayout для достижения настраиваемых размеров элементов, заданных в методе делегата.

Ответ 1

Нашел сам.

Внедрить пользовательский класс, не опуская UICollectionViewDelegateFlowLayout

@interface SECollectionViewCustomLayout : UICollectionViewFlowLayout 
                                          <UICollectionViewDelegateFlowLayout>

а затем вы можете позвонить

CGSize size = [self collectionView:self.collectionView 
                            layout:self 
            sizeForItemAtIndexPath:indexPath];

Ответ 2

Взгляд на различные заголовочные файлы UICollectionView... и просмотр WWDC 2012 Session 219 - Расширенные коллекции и создание пользовательских макетов (примерно с 6:50 и далее)), кажется, что расширяемый шаблон делегата использует динамическую типизацию, чтобы обеспечить правильное обращение макета к его расширенным методам делегатов.


Короче...

  • Если вы определяете собственный макет с собственным делегатом, определите этот делегат-протокол в файле заголовка макета.
  • Ваш объект-делегат (обычно UI(Collection)ViewController, который управляет представлением коллекции) должен заявить о себе, чтобы поддерживать этот настраиваемый протокол.
    • В том случае, если ваш макет - это просто UICollectionViewFlowLayout или его подкласс, это просто означает объявление соответствия UICollectionViewDelegateFlowLayout.
    • Не стесняйтесь делать это в расширении вашего класса в файле .m, если вы предпочитаете #import заголовок макета в интерфейсе делегата.
  • Чтобы получить доступ к методам делегата из макета, вызовите делегата представления коллекции.
    • Используйте свойство layout collectionView и передайте делегат объекту, соответствующему требуемому протоколу, чтобы убедить компилятор.
    • Не забудьте проверить, что делегат respondsToSelector: как обычно перед вызовом дополнительных методов делегата. На самом деле, если вам нравится, нет никакого вреда для этого для всех методов, поскольку приведение типов означает, что нет гарантии выполнения, делегат даже реализует необходимые методы.


В коде...

Итак, если вы реализуете собственный макет, который требует делегата для некоторой информации, ваш заголовок может выглядеть примерно так:

@protocol CollectionViewDelegateCustomLayout <UICollectionViewDelegate>
- (BOOL)collectionView:(UICollectionView *)collectionView
                layout:(UICollectionViewLayout *)layout
shouldDoSomethingMindblowingAtIndexPath:(NSIndexPath *)indexPath;
@end

@interface CustomLayout : UICollectionViewLayout
// ...
@end


Ваш делегат объявляет о соответствии (я сделал это в файле реализации здесь):

#import "CustomLayout.h"

@interface MyCollectionViewController () <CollectionViewDelegateCustomLayout>
@end

@implementation
// ...
- (BOOL)collectionView:(UICollectionView *)collectionView
                layout:(UICollectionViewLayout *)layout
shouldDoSomethingMindblowingAtIndexPath:(NSIndexPath *)indexPath
{
    return [self canDoSomethingMindblowing];
}
// ...
@end


И в вашей реализации макета вы получаете доступ к следующему методу:

BOOL blowMind;
if ([self.collectionView.delegate respondsToSelector:@selecor(collectionView:layout:shouldDoSomethingMindblowingAtIndexPath:)]) {
    blowMind = [(id<CollectionViewDelegateCustomLayout>)self.collectionView.delegate collectionView:self.collectionView
                                                                                             layout:self
                                                            shouldDoSomethingMindblowingAtIndexPath:indexPath];
} else {
    // Perhaps the layout also has a property for this, if the delegate
    // doesn't support dynamic layout properties...?
    // blowMind = self.blowMind;
}

Обратите внимание, что это безопасно для typecast здесь, поскольку мы проверяем, что делегат отвечает на этот метод заранее.


Свидетельство...

Это только предположение, но я подозреваю, что Apple управляет протоколом UICollectionViewDelegateFlowLayout.

  • В макете потока нет свойства delegate, поэтому вызовы должны проходить через делегат представления коллекции.
  • UICollectionViewController не является публично совместимым с расширенным делегатом макета потока (и я сомневаюсь, что он делает это в другом закрытом заголовке).
  • UICollectionView delegate свойство объявляет только соответствие базовому протоколу UICollectionViewDelegate. Опять же, я сомневаюсь, что существует частный подкласс/категория UICollectionView, используемый макетом потока, чтобы предотвратить необходимость приведения типов. Чтобы добавить дополнительный вес к этому моменту, Apple отказывается от подклассификации UICollectionView вообще в документах (Руководство по программированию коллекции для iOS: Создание пользовательских макетов):

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

Итак, идем. Не сложно, но стоит знать, как сделать это с помощью парадигмы.

Ответ 3

Существует быстрая версия:

self.collectionView(self.collectionView, layout: self.collectionView.collectionViewLayout, sizeForItemAtIndexPath: indexPath)

Ответ 4

Просмотрите UICollectionView-FlowLayout в GitHub. То же самое, это просто делает доступ к расширенным методам делегата flowLayout немного чище.

Ответ 5

Для более поздних читателей IOS 7 имеет UICollectionViewFlowLayout, который определил его.

Ответ 6

В моем случае все, что касается макета, компоновки ячеек и т.д., определяется внутри nib для UIViewController и отдельного nib для UICollectionViewCell. MyCollectionViewCell содержит UIImageView с автозапуском в ячейку с заполнением/полями, но с квадратной формой.

Мне нужны круглые значки, а не квадрат, но я не хочу заботиться о том, какой я использую для iPhone или iPad (у меня есть отдельные наконечники для устройств и для ориентации).

Я не хочу внедрять @selector(collectionView:layout:sizeForItemAtIndexPath:) в свой контроллер представления.

Итак, внутри collectionView:cellForItemAtIndexPath: Я могу просто использовать

CGSize size = cell.imageView.bounds.size;
cell.imageView.layer.masksToBounds = YES;
cell.imageView.layer.cornerRadius = size.height/2.0;

Потому что collectionView:layout:sizeForItemAtIndexPath: вызов до collectionView:cellForItemAtIndexPath: и макет сделанный.

Вы можете проверить круглые аватары внизу

введите описание изображения здесь