Ячейки UICollectionView Self Sizing с автоматической разметкой

207

Я пытаюсь настроить самоконтроль при UICollectionViewCellsработе с Auto Layout, но я не могу заставить ячейки соответствовать размеру контента. У меня возникли проблемы с пониманием того, как размер ячейки обновляется из содержимого содержимого содержимого ячейки.

Вот настройки, которые я пробовал:

  • Пользовательский UICollectionViewCellс UITextViewего в contentView.
  • Прокрутка для UITextViewотключена.
  • Горизонтальное ограничение contentView: «H: | [_textView (320)]», т. Е. Закреплено UITextViewслева от ячейки с явной шириной 320.
  • Вертикальное ограничение contentView: «V: | -0 - [_ textView]», то есть UITextViewзакреплено в верхней части ячейки.
  • UITextViewИмеет набор высоты ограничение на константу которойUITextView отчеты будут соответствовать тексту.

Вот как это выглядит с красным цветом фона ячейки, а UITextView синим фона: красный фон ячейки, синий фон UITextView

Я разместил проект, с которым я играл, на GitHub здесь .

rawbee
источник
Вы реализуете метод sizeForItemAtIndexPath?
Даниэль Галаско
@DanielGalasko Я нет. Насколько я понимаю, новая функция саморазмера ячеек в iOS 8 больше не требует от вас этого.
Rawbee
не знаю, откуда вы взяли эту информацию, но вам все еще нужно, взгляните на выдержку из WWDC asciiwwdc.com/2014/sessions/226
Даниэль Галаско
но опять же, это зависит от вашего
варианта
1
2019 - Важно взглянуть на это: stackoverflow.com/a/58108897/294884
Толстяк

Ответы:

309

Обновлено для Swift 5

preferredLayoutAttributesFittingAttributesпереименовать preferredLayoutAttributesFittingи использовать авторазмер


Обновлено для Swift 4

systemLayoutSizeFittingSize переименован в systemLayoutSizeFitting


Обновлено для iOS 9

Увидев, что мой GitHub-решение сломалось под iOS 9, у меня наконец-то появилось время полностью исследовать проблему. Сейчас я обновил репо, чтобы включить несколько примеров различных конфигураций для ячеек с самоконтролем. Мой вывод состоит в том, что самоконтролирующие клетки хороши в теории, но на практике беспорядочные. Слово предостережения при продолжении самоконтроля клеток.

TL; DR

Проверьте мой проект GitHub


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

Есть две вещи, которые вам нужно настроить для работы с саморазмерными ячейками.

1. Установите estimatedItemSizeнаUICollectionViewFlowLayout

Макет потока станет динамичным по своей природе после того, как вы установите estimatedItemSizeсвойство.

self.flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

2. Добавить поддержку для определения размеров на вашем подклассе ячейки

Это входит в 2 аромата; Авто-макет или пользовательское переопределение preferredLayoutAttributesFittingAttributes.

Создание и настройка ячеек с помощью Auto Layout

Я не буду вдаваться в подробности об этом, поскольку есть замечательный пост SO о настройке ограничений для ячейки. Просто будьте осторожны, что Xcode 6 сломал кучу вещей с iOS 7, поэтому, если вы поддерживаете iOS 7, вам нужно будет сделать что-то вроде того, чтобы убедиться, что autoresizingMask установлен в contentView ячейки, а границы contentView установлены как границы ячейки, когда ячейка загружена (т.е. awakeFromNib).

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

Внедрите preferredLayoutAttributesFittingAttributesв свою ячейку

Когда эта функция вызывается, ваше представление уже было настроено с содержимым (то cellForItemесть было вызвано). Предполагая, что ваши ограничения были установлены соответствующим образом, у вас может быть такая реализация:

//forces the system to do one layout pass
var isHeightCalculated: Bool = false

override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
    //Exhibit A - We need to cache our calculation to prevent a crash.
    if !isHeightCalculated {
        setNeedsLayout()
        layoutIfNeeded()
        let size = contentView.systemLayoutSizeFitting(layoutAttributes.size)
        var newFrame = layoutAttributes.frame
        newFrame.size.width = CGFloat(ceilf(Float(size.width)))
        layoutAttributes.frame = newFrame
        isHeightCalculated = true
    }
    return layoutAttributes
}

ПРИМЕЧАНИЕ В iOS 9 поведение немного изменилось, что может привести к сбоям в вашей реализации, если вы не будете осторожны (подробнее здесь ). При реализации preferredLayoutAttributesFittingAttributesвам необходимо убедиться, что вы изменяете фрейм атрибутов макета только один раз. Если вы этого не сделаете, макет будет вызывать вашу реализацию неопределенно долго и в конечном итоге вылетит. Одним из решений является кэширование вычисленного размера в вашей ячейке и аннулирование этого в любое время, когда вы повторно используете ячейку или меняете ее содержимое, как я сделал сisHeightCalculated свойством.

Испытайте свой макет

На этом этапе у вас должны быть «функционирующие» динамические ячейки в вашем collectionView. Я еще не нашел готового решения, достаточного для моих тестов, поэтому не стесняйтесь комментировать, если у вас есть. По-прежнему ощущается, что UITableViewвыигрывает битву за динамические размеры ИМХО.

Предостережения

Помните, что если вы используете ячейки-прототипы для вычисленияtimateItemSize - это сломается, если ваша XIB использует классы размеров . Причина этого заключается в том, что при загрузке ячейки из XIB ее класс размера будет настроен с помощью Undefined. Это будет нарушено только в iOS 8 и выше, поскольку в iOS 7 класс размера будет загружаться в зависимости от устройства (iPad = обычный-любой, iPhone = компактный-любой). Вы можете либо установитьtimateItemSize без загрузки XIB, либо вы можете загрузить ячейку из XIB, добавить ее в collectionView (это установит traitCollection), выполнить макет, а затем удалить его из суперпредставления. В качестве альтернативы вы также можете заставить свою ячейку переопределять traitCollectionгеттер и возвращать соответствующие черты. Тебе решать.

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


Даниэль Галаско
источник
2
Я пытаюсь реализовать то же самое с макетом потока, однако, когда я возвращаю его newFrame с обновленным размером, макет, похоже, не пересчитывает позиции y, которые по-прежнему основаны на высоте в valuesSize. Чего не хватает?
Анна
@ Вы звоните invalidateLayout на макете?
Даниэль Галаско
1
Ах, нашел разницу, вы установили предполагаемую высоту довольно высоко (400), тогда как я делал минимум (44). Это помогло. Но, кажется, что contentSize по-прежнему не настроен правильно, пока вы не прокрутите весь этот путь, поэтому все еще не очень удобен. Кстати, я изменил UITextView на UILabel, то же самое поведение.
Анна
17
Похоже, что это принципиально сломано на iOS 9 GM. Установка estimatedItemSizeприводит к сбоям - кажется, что в том, как автоматическая разметка пытается обработать UICollectionViewCell, есть огромная ошибка.
Кампания Уэса
3
Столкнувшись с аналогичной проблемой, кто-нибудь нашел обходной путь?
Тони
47

В iOS10 есть новая константа, названная UICollectionViewFlowLayout.automaticSize(ранее UICollectionViewFlowLayoutAutomaticSize), поэтому вместо этого:

self.flowLayout.estimatedItemSize = CGSize(width: 100, height: 100)

Вы можете использовать это:

self.flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

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

Доступ к макету потока:

override func viewDidLoad() {
   super.viewDidLoad()

   if let flowLayout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
      flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
   }
}

Swift 5 Обновлено:

override func viewDidLoad() {
   super.viewDidLoad()

   if let flowLayout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
      flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
    }
}
pawel221
источник
8
Где вы это установите? в viewdidload? Как вы справляетесь с flowlayout?
UKDataGeek
1
Мне удалось реализовать это - но у него странное поведение, которое заставляет его центрировать ячейки, если есть одна ячейка. Вот вопрос переполнения стека об этом - поставил меня в тупик: stackoverflow.com/questions/43266924/…
UKDataGeek
1
Вы можете получить доступ к макету потока через collectionViews collectionViewLayoutи просто проверить, что он имеет типUICollectionViewFlowLayout
d4Rk
4
это делает мои клетки очень маленькими, бесполезными
user924
44

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

На шаге 1 при использовании автоматического макета просто добавьте один родительский UIView в ячейку. ВСЕ внутри клетки должно быть подвидом родителя. Это ответило на все мои проблемы. Хотя XCode добавляет это для UITableViewCells автоматически, этого не происходит (но должно быть) для UICollectionViewCells. Согласно документам :

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

Затем полностью пропустите шаг 3. Это не нужно

Мэтт Коала
источник
1
Интересный; это специально для Xibs, но, тем не менее, спасибо, постараюсь запутаться в проекте Github и посмотреть, смогу ли я воспроизвести. Может быть, разделить ответ на Xib против программного
Даниэль Галаско
Очень полезно . Спасибо!
ProblemSlover
2
iOS 9, Xcode 7. Ячейки прототипированы в раскадровке, установлены собственные подклассы. Попытавшись создать свойство под названием contentView, XCode жалуется, что оно конфликтует с существующим свойством. Попытка добавить подпредставления self.contentViewи установить ограничения для этого, а затем приложение вылетает.
Николас Миари
@NicolasMiari Я не знаю, как это сделать программно, мое решение на самом деле просто добавить один пользовательский интерфейс в XIB, где все внутри него. Вам не нужно создавать собственность или что-то еще.
Мэтт Коала
Я сталкиваюсь с той же проблемой, что и @NicolasMiari. Когда я устанавливаю предполагаемое значение ContentSize для ячеек, прототипированных в раскадровке, с ограничениями автопоставки в iOS 9 / Xcode 7, приложение вылетает с ошибкой плохого доступа и без полезной трассировки стека
wasabi
34

В iOS 10+ это очень простой двухэтапный процесс.

  1. Убедитесь, что все содержимое вашей ячейки размещено в одном UIView (или внутри потомка UIView, такого как UIStackView, который значительно упрощает автоматическое расположение). Как и в случае динамического изменения размера UITableViewCells, для всей иерархии представления должны быть настроены ограничения, от самого внешнего контейнера до самого внутреннего представления. Это включает в себя ограничения между UICollectionViewCell и непосредственным дочерним видом

  2. Поручите разметку вашего UICollectionView, чтобы размер автоматически

    yourFlowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
Marmoy
источник
Мне интересно, как бы обернуть содержимое ваших ячеек в UIView сделать Auto Layout лучше? Я думал, что это будет работать лучше с более мелкой иерархией?
Джеймс Хилд
1
Я не упомянул производительность, только простоту. Саморазмерные ячейки будут работать, только если у вас есть ограничения, охватывающие весь путь между левым и правым, а также между верхом и низом. Достигнуть этого проще всего, когда все представления помещены в один контейнер. Если этот контейнер является UIStackView, то проще, если он является любым другим потомком UIView.
Marmoy
Подскажите, пожалуйста, как установить постоянную высоты (например, установить высоту 25, и ширина будет автоматически изменена) для UICollectionViewFlowLayoutAutomaticSize.
Святослав Атанасов
Используя Swift 4.1, Xcode сообщает, UICollectionViewFlowLayout.automaticSizeчто был переименован в UICollectionViewFlowLayoutAutomaticSize.
LinusGeffarth
14
  1. Добавить flowLayout для viewDidLoad ()

    override func viewDidLoad() {
        super.viewDidLoad() 
        if let flowLayout = infoCollection.collectionViewLayout as? UICollectionViewFlowLayout {
            flowLayout.estimatedItemSize = CGSize(width: 1, height:1)
        }
    }
  2. Кроме того, установите UIView в качестве mainContainer для вашей ячейки и добавьте в нее все необходимые представления.

  3. Обратитесь к этому удивительному, умопомрачительному учебнику для дальнейшей справки: UICollectionView с авторазмером ячейки с использованием автоматического размещения в iOS 9 и 10

dianakarenms
источник
11

РЕДАКТИРОВАНИЕ 19/19/19: Для iOS 13, просто используйте UICollectionViewCompositionalLayout с предполагаемой высотой. Не тратьте свое время на работу с этим неработающим API.

Поработав с этим в течение некоторого времени, я заметил, что изменение размера не работает для UITextViews, если вы не отключите прокрутку:

let textView = UITextView()
textView.scrollEnabled = false
noobular
источник
при попытке прокрутки ячейка представления коллекции не является гладкой. У вас есть какое-то решение для этого?
Сатиш Кумар Гурунатхан
6

тайна привязки contentView:

В одном странном случае это

    contentView.translatesAutoresizingMaskIntoConstraints = false

не будет работать. Добавили четыре явных якоря к contentView, и это сработало.

class AnnoyingCell: UICollectionViewCell {
    
    @IBOutlet var word: UILabel!
    
    override init(frame: CGRect) {
        super.init(frame: frame); common() }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder); common() }
    
    private func common() {
        contentView.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            contentView.leftAnchor.constraint(equalTo: leftAnchor),
            contentView.rightAnchor.constraint(equalTo: rightAnchor),
            contentView.topAnchor.constraint(equalTo: topAnchor),
            contentView.bottomAnchor.constraint(equalTo: bottomAnchor)
        ])
    }
}

и как обычно

    estimatedItemSize = UICollectionViewFlowLayout.automaticSize

в YourLayout: UICollectionViewFlowLayout

Кто знает? Может помочь кому-нибудь.

кредит

https://www.vadimbulavin.com/collection-view-cells-self-sizing/

наткнулся на верхушку там - никогда не видел его больше нигде во всех тысячах статей об этом.

Fattie
источник
3

Я сделал динамическую высоту ячейки представления коллекции. Вот репозиторий git hub .

И выясните, почему предпочитаемое значениеLayoutAttributesFittingAttributes вызывается более одного раза. На самом деле, он будет вызван как минимум 3 раза.

Изображение журнала консоли: введите описание изображения здесь

1-й предпочтительныйLayoutAttributesFittingAttributes :

(lldb) po layoutAttributes
<UICollectionViewLayoutAttributes: 0x7fa405c290e0> index path: (<NSIndexPath:    0xc000000000000016> 
{length = 2, path = 0 - 0}); frame = (15 12; 384 57.5); 

(lldb) po self.collectionView
<UICollectionView: 0x7fa40606c800; frame = (0 57.6667; 384 0);

LayoutAttributes.frame.size.height - текущий статус 57,5 .

2-й предпочитаемыйLayoutAttributesFittingAttributes :

(lldb) po layoutAttributes
<UICollectionViewLayoutAttributes: 0x7fa405c16370> index path: (<NSIndexPath: 0xc000000000000016> 
{length = 2, path = 0 - 0}); frame = (15 12; 384 534.5); 

(lldb) po self.collectionView
<UICollectionView: 0x7fa40606c800; frame = (0 57.6667; 384 0);

Высота каркаса ячейки изменилась до 534,5, как и ожидалось. Но представление коллекции все еще нулевой высоты.

3-й предпочитаемыйLayoutAttributesFittingAttributes :

(lldb) po layoutAttributes
<UICollectionViewLayoutAttributes: 0x7fa403d516a0> index path: (<NSIndexPath: 0xc000000000000016> 
{length = 2, path = 0 - 0}); frame = (15 12; 384 534.5); 

(lldb) po self.collectionView
<UICollectionView: 0x7fa40606c800; frame = (0 57.6667; 384 477);

Вы можете видеть, что высота просмотра коллекции была изменена с 0 на 477 .

Поведение похоже на ручку прокрутки:

1. Before self-sizing cell

2. Validated self-sizing cell again after other cells recalculated.

3. Did changed self-sizing cell

Сначала я думал, что этот метод вызывается только один раз. Поэтому я кодировал следующее:

CGRect frame = layoutAttributes.frame;
frame.size.height = frame.size.height + self.collectionView.contentSize.height;
UICollectionViewLayoutAttributes* newAttributes = [layoutAttributes copy];
newAttributes.frame = frame;
return newAttributes;

Эта строка:

frame.size.height = frame.size.height + self.collectionView.contentSize.height;

вызовет бесконечный цикл системного вызова и сбой приложения.

При любом изменении размера он будет снова и снова проверять все предпочтительные ячейки selectedLayoutAttributesFittingAttributes до тех пор, пока позиции каждой ячейки (т. Е. Кадры) больше не изменятся.

Ян Янг
источник
2

В дополнение к ответам выше,

Просто убедитесь, что вы установили для свойства оценочного свойства UIollectionViewFlowLayout некоторый размер и не реализуете sizeForItem: atIndexPath делегата .

Вот и все.

Мурат Ясар
источник
1

Если вы реализуете метод UICollectionViewDelegateFlowLayout:

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

Когда вы звоните collectionview performBatchUpdates:completion:, sizeForItemAtIndexPathвместо размера будет использоваться высотаpreferredLayoutAttributesFittingAttributes .

Процесс рендеринга performBatchUpdates:completionбудет проходить через метод, preferredLayoutAttributesFittingAttributesно он игнорирует ваши изменения.

Ян Янг
источник
Я видел это неправильное поведение, но только когда предполагаемый размер равен нулю. Вы уверены, что устанавливаете приблизительный размер?
dgatwood
1

Кому бы это ни помогло,

У меня был этот неприятный сбой, если estimatedItemSizeбыл установлен. Даже если я вернул 0 в numberOfItemsInSection. Таким образом, сами ячейки и их автоматическое расположение не были причиной сбоя ... CollectionView просто сбой, даже если он пуст, просто потому, что estimatedItemSizeбыл настроен для самостоятельного определения размера.

В моем случае я реорганизовал свой проект, с контроллера, содержащего collectionView, на collectionViewController, и он работал.

Пойди разберись.

Жан Ле Муаньян
источник
1
Попробуйте позвонить collectionView.collectionViewLayout.invalidateLayout()после collectionView.reloadData().
chengsam
для ios10 и ниже добавьте этот код `переопределить func viewWillLayoutSubviews () {super.viewWillLayoutSubviews () если #available (iOS 11.0, *) {} else {mainCollectionView.collectionViewLayout.invalidateLayout ()}}`
Маросди Ума,
1

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

label.preferredMaxLayoutWidth = 200

Больше информации: здесь

Ура!

HelloimDarius
источник
0

Приведенный выше пример метода не компилируется. Вот исправленная версия (но не проверенная относительно того, работает ли она.)

override func preferredLayoutAttributesFittingAttributes(layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes 
{
    let attr: UICollectionViewLayoutAttributes = layoutAttributes.copy() as! UICollectionViewLayoutAttributes

    var newFrame = attr.frame
    self.frame = newFrame

    self.setNeedsLayout()
    self.layoutIfNeeded()

    let desiredHeight: CGFloat = self.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
    newFrame.size.height = desiredHeight
    attr.frame = newFrame
    return attr
}
user3246173
источник
0

Обновите больше информации:

  • Если вы используете flowLayout.estimatedItemSize, предложите использовать iOS8.3 более поздней версии. До iOS8.3 он вылетит [super layoutAttributesForElementsInRect:rect];. Сообщение об ошибке

    *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil'

  • Во-вторых, в версии iOS8.x, flowLayout.estimatedItemSizeне будет работать настройка вставки различных разделов. то есть функция: (UIEdgeInsets)collectionView:layout:insetForSectionAtIndex:.

Ян Янг
источник
0

Решение состоит из 4 важных шагов:

  1. Включение динамического размера ячейки

flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

  1. Явно включите автоматическую разметку и закрепите contentViewее по краям ячейки, поскольку она contentViewпредотвращает автоматическое изменение размера, учитывая, что для нее не включена автоматическая разметка.
class MultiLineCell: UICollectionViewCell{

    private var textViewHeightContraint: NSLayoutConstraint!

    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .cyan
        contentView.translatesAutoresizingMaskIntoConstraints = false
        contentViewWidthAnchor = contentView.widthAnchor.constraint(equalToConstant: 0)

        NSLayoutConstraint.activate([
            contentView.leadingAnchor.constraint(equalTo: leadingAnchor),
            contentView.topAnchor.constraint(equalTo: topAnchor),
            contentView.trailingAnchor.constraint(equalTo: trailingAnchor),
            contentView.bottomAnchor.constraint(equalTo: bottomAnchor),
        ])
    } 
    ...
}
  1. Установите contentView.widthAnchor.constraint из, collectionView(:cellForItemAt:)чтобы ограничить ширину contentView шириной collectionView.

Это происходит следующим образом; мы установим ячейку maxWidthпеременной ширину CollectionView по адресу collectionView(:cellForItemAt:)и в maxWidth«S didSetметода мы установим widthAnchor.constant в MaxWidth.

class ViewController: UIViewController, UICollectionViewDataSource {
    ...

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath) as! MultiLineCell
        cell.textView.text = dummyTextMessages[indexPath.row]
        cell.maxWidth = collectionView.frame.width
        return cell
    }

    ...
}
class MultiLineCell: UICollectionViewCell{
    ....

    var maxWidth: CGFloat? {
        didSet {
            guard let maxWidth = maxWidth else {
                return
            }
            contentViewWidthAnchor.constant = maxWidth
            contentViewWidthAnchor.isActive = true
        }
    }

    ....
}

Поскольку вы хотите включить самоопределение UITextView, у него есть дополнительный шаг к;

4. Рассчитайте и установите значение heightAnchor.constant для UITextView.

Таким образом, всякий раз , когда ширина contentView установлена , мы будем корректировать высоту UITextView вместе в didSetо maxWidth.

Внутри UICollectionViewCell:

var maxWidth: CGFloat? {
    didSet {
        guard let maxWidth = maxWidth else {
            return
        }
        contentViewWidthAnchor.constant = maxWidth
        contentViewWidthAnchor.isActive = true

        let sizeToFitIn = CGSize(width: maxWidth, height: CGFloat(MAXFLOAT))
        let newSize = self.textView.sizeThatFits(sizeToFitIn)
        self.textViewHeightContraint.constant = newSize.height
    }
}

Эти шаги принесут вам желаемый результат. Вот полная суть

Спасибо Вадиму Булавину за его ясное и понятное сообщение в блоге - Самостоятельная подборка элементов View Collection: пошаговое руководство

Harley
источник
-2

Я пытался использовать, estimatedItemSizeно при вставке и удалении ячеек было много ошибок, если они estimatedItemSizeне были точно равны высоте ячейки. я перестал устанавливатьestimatedItemSize и реализовал динамические ячейки, используя прототип ячейки. вот как это делается:

создайте этот протокол:

protocol SizeableCollectionViewCell {
    func fittedSize(forConstrainedSize size: CGSize)->CGSize
}

реализовать этот протокол в вашем обычае UICollectionViewCell:

class YourCustomCollectionViewCell: UICollectionViewCell, SizeableCollectionViewCell {

    @IBOutlet private var mTitle: UILabel!
    @IBOutlet private var mDescription: UILabel!
    @IBOutlet private var mContentView: UIView!
    @IBOutlet private var mTitleTopConstraint: NSLayoutConstraint!
    @IBOutlet private var mDesciptionBottomConstraint: NSLayoutConstraint!

    func fittedSize(forConstrainedSize size: CGSize)->CGSize {

        let fittedSize: CGSize!

        //if height is greatest value, then it's dynamic, so it must be calculated
        if size.height == CGFLoat.greatestFiniteMagnitude {

            var height: CGFloat = 0

            /*now here's where you want to add all the heights up of your views.
              apple provides a method called sizeThatFits(size:), but it's not 
              implemented by default; except for some concrete subclasses such 
              as UILabel, UIButton, etc. search to see if the classes you use implement 
              it. here's how it would be used:
            */
            height += mTitle.sizeThatFits(size).height
            height += mDescription.sizeThatFits(size).height
            height += mCustomView.sizeThatFits(size).height    //you'll have to implement this in your custom view

            //anything that takes up height in the cell has to be included, including top/bottom margin constraints
            height += mTitleTopConstraint.constant
            height += mDescriptionBottomConstraint.constant

            fittedSize = CGSize(width: size.width, height: height)
        }
        //else width is greatest value, if not, you did something wrong
        else {
            //do the same thing that's done for height but with width, remember to include leading/trailing margins in calculations
        }

        return fittedSize
    }
}

Теперь сделайте так, чтобы ваш контроллер соответствовал UICollectionViewDelegateFlowLayoutи имел в этом поле:

class YourViewController: UIViewController, UICollectionViewDelegateFlowLayout {
    private var mCustomCellPrototype = UINib(nibName: <name of the nib file for your custom collectionviewcell>, bundle: nil).instantiate(withOwner: nil, options: nil).first as! SizeableCollectionViewCell
}

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

наконец, UICollectionViewDelegateFlowLayout's collectionView(:layout:sizeForItemAt:)должно быть реализовано:

class YourViewController: UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {

    private var mDataSource: [CustomModel]

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath)->CGSize {

        //bind the prototype cell with the data that corresponds to this index path
        mCustomCellPrototype.bind(model: mDataSource[indexPath.row])    //this is the same method you would use to reconfigure the cells that you dequeue in collectionView(:cellForItemAt:). i'm calling it bind

        //define the dimension you want constrained
        let width = UIScreen.main.bounds.size.width - 20    //the width you want your cells to be
        let height = CGFloat.greatestFiniteMagnitude    //height has the greatest finite magnitude, so in this code, that means it will be dynamic
        let constrainedSize = CGSize(width: width, height: height)

        //determine the size the cell will be given this data and return it
        return mCustomCellPrototype.fittedSize(forConstrainedSize: constrainedSize)
    }
}

и это все. collectionView(:layout:sizeForItemAt:)Таким образом, возвращая размер ячейки, я не могу ее использовать estimatedItemSize, а вставка и удаление ячеек работает отлично.

башмак
источник