10.11 NSCollectionView — динамическое определение размера ячейки

Примечания к выпуску AppKit для OS X v10.11 предлагают что элементы представления коллекции могут быть изменены для каждого элемента:

Размер элемента может быть определен глобально для всех элементов CollectionView (путем установки свойства itemSize NSCollectionViewFlowLayout) или может варьироваться от одного элемента к другому (путем реализации -collectionView:layout:sizeForItemAtIndexPath: в вашем делегате CollectionView).

В моем случае мой CollectionViewItem состоит из одной метки, содержащей строку различной длины. Я использую NSCollectionView для отображения массива строк, поскольку NSStackViews не поддерживают привязки массивов и не переходят к новым строкам. Массив строк привязан к содержимому NSCollectionView через контроллер массива.

Файл пера моего элемента настроен правильно, корневой вид и метка имеют приоритеты Content Hugging и Content Compression Resistance, равные 1000, а края выровнены с помощью AutoLayout.

Теперь метод делегата NSCollectionViewLayout имеет эту подпись:

func collectionView(collectionView: NSCollectionView, 
                    layout collectionViewLayout: NSCollectionViewLayout, 
                    sizeForItemAtIndexPath indexPath: NSIndexPath) -> NSSize

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

let item = collectionView.itemAtIndexPath(indexPath)!
item.view.layout()
return item.view.bounds.size

Проблема с этим подходом заключается в том, что itemAtIndexPath возвращает nil. Если я возвращаю размер по умолчанию в случае nil, этот размер по умолчанию используется для всех ячеек.

Как я могу настроить NSCollectionView для соблюдения ограничений AutoLayout моего элемента и для каждой ячейки динамически использовать вычисленный размер?


person Etan    schedule 01.11.2015    source источник
comment
Моей быстрой мыслью было бы не получить элемент с помощью itemAtIndexPath. Вместо этого получите содержимое из своей модели и вместо этого рассчитайте высоту.   -  person Harry Ng    schedule 15.01.2016
comment
Элемент еще не находится в представлении коллекции, поэтому вы получаете nil обратно. Представление коллекции запрашивает размер, прежде чем добавить его в представление.   -  person Juul    schedule 30.08.2016
comment
Хорошее объяснение, но как бы вы справились с реальной проблемой применения ограничений AutoLayout?   -  person Etan    schedule 31.08.2016
comment
Меня это тоже интересует, и пока нет правильного ответа. Но я думаю, что AutoLayout всегда работает сверху вниз, и рамка устанавливается представлением коллекции независимо от любого AL. Все объятия и т. д. влияют только на представления внутри рамки элемента. Сам кадр элемента определяется исключительно CollectionViewLayout (или используемым методом делегата). Может быть, можно выделить только один дополнительный элемент только для целей определения размера, применить модель (установить представляемый объект), получить размер и вернуть его?   -  person hnh    schedule 03.10.2016


Ответы (1)


Есть дубликат этого вопроса, на который я ответил, но, вероятно, это тот, на который его следует направить, поскольку он старше.

Комментарий Юула правильный - предметов не существует. sizeForItemAt вызывается коллекция, запрашивающая у делегата какой-либо конкретный размер для этой записи данных, с которой он будет использовать, чтобы помочь создать свой возможный контроллер представления, NSCollectionViewItem. Таким образом, вы создаете цикл, когда просите коллекцию предоставить вам элемент в методе, который она использует для получения элемента.

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

Единственное решение, к которому я пришел, которое могло бы быть красивее, заключается в следующем:

Подготовка

  • Подкласс NSCollectionViewItem и убедитесь, что в представлении коллекции есть источник данных, который возвращает правильный элемент подкласса.
  • Полностью используйте ограничения в своем XIB.
  • Ваш подкласс должен иметь метод, который загружает объект данных, который будет представлен, как для этого, так и, конечно, для ваших методов протокола источника данных.
  • В какой-то момент до первого вызова sizeForItemAt или в начале первого, если вы еще этого не сделали, вручную создайте экземпляр вашего подкласса NSCollectionViewItem и используйте instantiate(withOwner:topLevelObjects:) NSNib для создания экземпляра его XIB с вашим подклассом в качестве владельца. Сохраните эту ссылку как своего рода «шаблон размеров», поэтому вам нужно будет сделать это только один раз. Делегат был самым легким местом для меня.

^Примечание: мой первый способ состоял в том, чтобы попытаться сделать это через makeItemWithIdentifier коллекции, но это было более хрупко, так как требовалось, чтобы коллекция содержала элементы во время создания шаблона размеров. Это также не могло быть сделано во время начального sizeForItemAt (доступ/создание предметов во время перезагрузки вылетает). И я был обеспокоен тем, что, поскольку он был сделан с коллекцией, он может быть повторно использован в будущем, а методы, описанные ниже, не работают или не начинают редактировать видимые элементы. YMMV.

In sizeForItemAt

  • Непосредственно получить объект данных, представляемый из источника данных. Пусть ваш объект шаблона размера представляет этот объект данных с помощью метода, о котором я упоминал ранее.
  • Получите доступ к View.FittingSize шаблона размеров, наименьшему размеру элемента, которому могут быть заданы его ограничения/приоритеты, и верните его.

Бам! Не проходил стресс-тестирование или что-то в этом роде, но никаких проблем с моей стороны, и он не выполняет проход макета или что-то в этом роде, просто вызывает FittingSize. Я еще не видел, чтобы это было сформулировано где-либо в Интернете, поэтому я хотел написать полное объяснение.

Я сделал это в Xamarin.Mac, поэтому мой код не будет 1:1, и я не хочу писать искаженный свифт и что-то испортить.

TLDR: вручную создайте экземпляр подкласса NSCollectionViewItem и его xib, которые вы будете хранить, не принадлежащие коллекции. Во время sizeForItem заполните этот элемент, который вы храните в качестве ссылки на размер, и верните FittingSize представления элемента коллекции.

person NickSpag    schedule 26.01.2018