UICollectionView самостоятельной размеров клеток с авто макет
Я пытаюсь получить сам размер UICollectionViewCells
работа с авто макет, но я не могу, кажется, чтобы получить ячейки, чтобы размер себя к содержимому. У меня возникли проблемы с пониманием того, как размер ячейки обновляется из содержимого того, что находится внутри contentView ячейки.
вот настройка, которую я пробовал:
- Custom
UICollectionViewCell
СUITextView
в его contentView. - скролл для
UITextView
отключено. - на странице просмотр содержимого это горизонтальное ограничение: "H: / [_textView (320)]", т. е.
UITextView
закрепляется слева от ячейки с явной шириной 320.
на странице просмотр содержимого является: "V:/-0-[_textView]", т. е. - The
UITextView
имеет ограничение по высоте, установленное на константу, котораяUITextView
отчеты будут соответствовать тексту.
UITextView
прикреплен к верхней части ячейки.
вот как это выглядит с фоном ячейки, установленным в красный цвет, и UITextView
фон установить на синий:
я поставил проект, с которым я играл на GitHub здесь.
13 ответов:
обновлено для Swift 4
systemLayoutSizeFittingSize
переименован вsystemLayoutSizeFitting
обновлено для iOS 9
увидев, что мое решение GitHub сломалось под iOS 9, я, наконец, получил время, чтобы полностью изучить проблему. Теперь я обновил репо, чтобы включить несколько примеров различных конфигураций для ячеек с самостоятельным размером. Мой вывод заключается в том, что самостоятельная проклейка клетки являются большими в теории, а на практике лажа. Слово предостережения при продолжении самостоятельной калибровки ячейки.
TL; DR
проверьте мои проект GitHub
сам размер ячейки поддерживаются только с макетом потока, поэтому убедитесь, что это то, что вы используете.
есть две вещи, которые вам нужно настроить для самостоятельной калибровки ячеек для работы.
1. Набор
estimatedItemSize
onUICollectionViewFlowLayout
макет потока станет динамическим по своей природе, как только вы установите
estimatedItemSize
собственность.self.flowLayout.estimatedItemSize = CGSize(width: 100, height: 100)
2. Добавьте поддержку для определения размера на вашем подклассе ячейки
это поставляется в 2 вкусов; авто-макет или пользовательские переопределения
preferredLayoutAttributesFittingAttributes
.создание и настройка ячеек с автоматической компоновкой
я не буду вдаваться в подробности об этом, как есть блестящий так пост о настройке ограничений для ячейки. Просто будьте осторожны, что 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 preferredLayoutAttributesFittingAttributes(_ 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
выигрывает битву за динамическую калибровку ИМХО.предостережения
будьте очень внимательны, что если вы используете прототип ячейки для расчета estimatedItemSize - это сломается, если ваш XIB использует классы размере. Причина этого в том, что когда вы загружаете свою ячейку из XIB его размер класса будет настроен с
Undefined
. Это будет нарушено только на iOS 8 и выше, так как на iOS 7 класс размера будет загружен на основе устройства (iPad = обычный-любой, iPhone = компактный-любой). Вы можете либо установить estimatedItemSize без загрузки XIB, либо вы можете загрузить ячейку из XIB, добавить ее в collectionView (это установит traitCollection), выполнить макет, а затем удалить его из супервизора. В качестве альтернативы вы также можете сделать свою ячейку переопределитьtraitCollection
getter и возвращает соответствующие черты. Это зависит от тебя.Дайте мне знать, если я что-то пропустил, надеюсь, что я помог и удачи кодирования
несколько ключевых изменений ответ Даниила Галаско исправлены все мои проблемы. К сожалению, у меня недостаточно репутации, чтобы комментировать напрямую (пока).
в шаге 1, при использовании автоматической компоновки, просто добавьте один родительский UIView в ячейку. Все внутри ячейки должно быть подвидом родителя. Это ответило на все мои проблемы. Хотя XCode добавляет Это для UITableViewCells автоматически, это не так (но должно быть) для UICollectionViewCells. По данным docs:
чтобы настроить внешний вид ячейки, добавьте представления, необходимые для представления содержимого элемента данных в виде вложенных представлений в представление в свойстве contentView. Не добавляйте непосредственно вложенные представления в саму ячейку.
затем полностью пропустите шаг 3. В этом нет необходимости.
В iOS10 есть новая константа под названием
UICollectionViewFlowLayoutAutomaticSize
, Так что вместо этого:self.flowLayout.estimatedItemSize = CGSize(width: 100, height: 100)
вы можете использовать это:
self.flowLayout.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize
он имеет лучшую производительность, особенно когда ячейки в вашем представлении коллекции имеют постоянный wid
в iOS 10+ это очень простой 2 этапа.
убедитесь, что все содержимое вашей ячейки помещено в один UIView (или внутри потомка UIView, такого как UIStackView, что значительно упрощает автозапуск). Так же, как и при динамическом изменении размера UITableViewCells, вся иерархия представлений должна иметь ограничения, настроенные от самого внешнего контейнера до самого внутреннего представления. Это включает ограничения между UICollectionViewCell и непосредственным childview
проинструктируйте flowlayout вашего UICollectionView к размеру автоматически
yourFlowLayout.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize
добавить flowLayout на viewDidLoad ()
override func viewDidLoad() { super.viewDidLoad() if let flowLayout = infoCollection.collectionViewLayout as? UICollectionViewFlowLayout { flowLayout.estimatedItemSize = CGSize(width: 1, height:1) } }
кроме того, установите UIView в качестве mainContainer для своей ячейки и добавьте в нее все необходимые представления.
см. Этот удивительный, умопомрачительный учебник для дальнейшего использования: UICollectionView с ячейкой авторазмера с помощью autolayout в iOS 9 & 10
после борьбы с этим в течение некоторого времени, я заметил, что изменение размера не работает для UITextViews, если вы не отключите прокрутку:
let textView = UITextView() textView.scrollEnabled = false
Я сделал динамическую высоту ячейки представления коллекции. Вот это Git hub repo.
и, выкопать, почему preferredLayoutAttributesFittingattributes вызывается несколько раз. На самом деле, он будет называться не менее 3 раз.
1-й preferredLayoutAttributesFittingattributes:
(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.рамка.размер.высота-это текущее состояние 57.5.
2-й preferredLayoutAttributesFittingattributes:
(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-й preferredLayoutAttributesFittingattributes:
(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;
вызовет бесконечный цикл системного вызова и сбой приложения.
любой размер изменен, он будет проверять все ячейки preferredLayoutAttributesFittingattributes снова и снова, пока позиции каждой ячейки (т. е. фреймы) больше не изменятся.
кому бы это ни помогло,
у меня была эта неприятная авария, если
estimatedItemSize
был установлен. Даже если я вернул 0 вnumberOfItemsInSection
. Таким образом, сами ячейки и их автоматическая компоновка не были причиной аварии... В collectionView просто разбился, даже когда пустой, просто потому чтоestimatedItemSize
был установлен для самостоятельной калибровки.в моем случае я реорганизовал свой проект, от контроллера, содержащего collectionView к collectionViewController, и это сработало.
Go фигура.
приведенный выше пример метода не компилируется. Вот исправленная версия (но непроверенная относительно того, работает ли она.)
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 }
если вы реализуете метод UICollectionViewDelegateFlowLayout:
- (CGSize)collectionView:(UICollectionView*)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath*)indexPath
когда вы называете
collectionview performBatchUpdates:completion:
, высота размера будет использоватьsizeForItemAtIndexPath
вместоpreferredLayoutAttributesFittingAttributes
.процесс визуализации
performBatchUpdates:completion
пройдет через методpreferredLayoutAttributesFittingAttributes
но он игнорирует ваши изменения.
обновить дополнительную информацию:
если вы используете
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:
.
для тех, кто пробовал все без удачи, это единственное, что заставило его работать на меня. Для многострочных меток внутри ячейки попробуйте добавить эту волшебную строку:
label.preferredMaxLayoutWidth = 200
Подробнее: здесь
Ура!
Я пробовал использовать
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
, и вставка и удаление ячеек отлично работает.