Це звучить як те, що ви просите, це спосіб використовувати UICollectionView для створення макета типу UITableView. Якщо це дійсно те, що ви хочете, правильний спосіб зробити це за допомогою спеціального підкласу UICollectionViewLayout (можливо, щось на зразок SBTableLayout ).
З іншого боку, якщо ви справді запитуєте, чи існує чистий спосіб зробити це за допомогою типового UICollectionViewFlowLayout за замовчуванням, то я вважаю, що немає можливості. Навіть із саморозмірними осередками iOS8 це не просто. Основна проблема, як ви кажете, полягає в тому, що механізм компонування потоку не дає можливості виправити один вимір і дати можливість іншим реагувати. (Крім того, навіть якщо ви могли б, виникне додаткова складність у тому, що потрібно мати два пропуски макета для розміщення багаторядкових міток. Це може не відповідати тому, як комірки для самостійного розміру хочуть обчислити всі розміри за допомогою одного виклику до systemLayoutSizeFittingSize.)
Однак якщо ви все-таки хочете створити макет, схожий на перегляд таблиці, з макетом потоку, з клітинками, які визначають власний розмір, і природно реагувати на ширину подання колекції, звичайно, це можливо. Ще існує безладний шлях. Я зробив це з "осередком розміру", тобто з не відображеним UICollectionViewCell, який контролер зберігає лише для обчислення розмірів комірок.
У цьому підході є дві частини. Перша частина полягає в тому, щоб делегат подання колекції обчислив правильний розмір комірки, взявши ширину подання колекції та використовуючи осередок розміру для обчислення висоти комірки.
У своєму UICollectionViewDelegateFlowLayout ви реалізуєте такий метод:
func collectionView(collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize
{
// NOTE: here is where we say we want cells to use the width of the collection view
let requiredWidth = collectionView.bounds.size.width
// NOTE: here is where we ask our sizing cell to compute what height it needs
let targetSize = CGSize(width: requiredWidth, height: 0)
/// NOTE: populate the sizing cell's contents so it can compute accurately
self.sizingCell.label.text = items[indexPath.row]
let adequateSize = self.sizingCell.preferredLayoutSizeFittingSize(targetSize)
return adequateSize
}
Це призведе до того, що в представленні колекції буде встановлено ширину комірки на основі поданого виду колекції, але потім попросіть осередок розміру обчислити висоту.
Друга частина полягає в тому, щоб отримати осередки розміру, щоб використовувати власні обмеження AL для обчислення висоти. Це може бути складніше, ніж це повинно бути, оскільки спосіб багаторядкової UILabel вимагає двоступеневого планування. Робота виконується методом preferredLayoutSizeFittingSize
, який є таким:
/*
Computes the size the cell will need to be to fit within targetSize.
targetSize should be used to pass in a width.
the returned size will have the same width, and the height which is
calculated by Auto Layout so that the contents of the cell (i.e., text in the label)
can fit within that width.
*/
func preferredLayoutSizeFittingSize(targetSize:CGSize) -> CGSize {
// save original frame and preferredMaxLayoutWidth
let originalFrame = self.frame
let originalPreferredMaxLayoutWidth = self.label.preferredMaxLayoutWidth
// assert: targetSize.width has the required width of the cell
// step1: set the cell.frame to use that width
var frame = self.frame
frame.size = targetSize
self.frame = frame
// step2: layout the cell
self.setNeedsLayout()
self.layoutIfNeeded()
self.label.preferredMaxLayoutWidth = self.label.bounds.size.width
// assert: the label's bounds and preferredMaxLayoutWidth are set to the width required by the cell's width
// step3: compute how tall the cell needs to be
// this causes the cell to compute the height it needs, which it does by asking the
// label what height it needs to wrap within its current bounds (which we just set).
let computedSize = self.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
// assert: computedSize has the needed height for the cell
// Apple: "Only consider the height for cells, because the contentView isn't anchored correctly sometimes."
let newSize = CGSize(width:targetSize.width,height:computedSize.height)
// restore old frame and preferredMaxLayoutWidth
self.frame = originalFrame
self.label.preferredMaxLayoutWidth = originalPreferredMaxLayoutWidth
return newSize
}
(Цей код адаптований із зразкового коду Apple із зразкового коду сесії WWDC2014 у розділі "Розширений перегляд колекції".)
Пара помічає. Він використовує layoutIfNeeded () для примусового розташування всієї комірки, щоб обчислити та встановити ширину мітки. Але цього недостатньо. Я вважаю, що вам також потрібно встановити preferredMaxLayoutWidth
так, щоб мітка використовувала цю ширину за допомогою автоматичного макета. І тільки тоді ви можете використовувати systemLayoutSizeFittingSize
для того, щоб змусити клітинку обчислити її висоту, враховуючи мітку.
Чи подобається мені такий підхід? Немає!! Це здається занадто складним, і він робить макет двічі. Але поки продуктивність не стає проблемою, я краще виконувати макет двічі під час виконання, ніж доводиться визначати її двічі в коді, що, здається, є єдиною іншою альтернативою.
Я сподіваюсь, що врешті-решт самоклеючі клітини працюватимуть інакше, і все це стане набагато простішим.
Приклад проекту, що показує його на роботі.
Але чому б не просто використовувати саморозмірні клітини?
Теоретично нові засоби iOS8 для "саморозмірних комірок" повинні зробити це непотрібним. Якщо ви визначили клітинку за допомогою автоматичного макета (AL), то вигляд колекції повинен бути досить розумним, щоб дозволити розміру і викласти правильно. На практиці я не бачив жодних прикладів, які б змусили це працювати з багаторядковими мітками. Я думаю, що це частково тому, що механізм самовимірювання клітин все ще є помилковим.
Але я б сказав, що це здебільшого через звичайну хитрість автоматичного розкладу та міток, а саме те, що для UILabels потрібен в основному процес двоетапної компонування. Мені незрозуміло, як можна виконати обидва кроки за допомогою самостійних розмірів комірок.
І як я вже говорив, це справді робота для іншого планування. Це суть потоку компонування потоку в тому, що він розміщує речі, які мають розмір, а не фіксує ширину і дозволяє їм вибирати висоту.
А як щодо переважнихLayoutAttributesFittingAttributes:?
preferredLayoutAttributesFittingAttributes:
Метод є відволікаючим маневром, я думаю. Це лише там, де застосовується новий механізм саморозмірного осередку. Отже, це не відповідь, якщо цей механізм є ненадійним.
А що з systemlayoutSizeFittingSize :?
Ти маєш рацію, що документи плутають.
Документи про те systemLayoutSizeFittingSize:
і systemLayoutSizeFittingSize:withHorizontalFittingPriority:verticalFittingPriority:
те, і інше пропонують вам пройти тільки UILayoutFittingCompressedSize
і UILayoutFittingExpandedSize
як targetSize
. Однак сам підпис методу, заголовок коментарів та поведінка функцій вказують на те, що вони відповідають точному значенню targetSize
параметра.
Насправді, якщо ви встановите UICollectionViewFlowLayoutDelegate.estimatedItemSize
для того, щоб увімкнути новий механізм саморозмірної комірки, це значення, здається, передається як targetSize. І, UILabel.systemLayoutSizeFittingSize
здається, повертає точно такі ж значення, як і UILabel.sizeThatFits
. Це підозріло, враховуючи, що аргумент до systemLayoutSizeFittingSize
повинен бути грубою ціллю, а аргумент до sizeThatFits:
повинен бути максимально обмежуючим розміром.
Більше ресурсів
Хоча сумно думати, що така рутинна вимога повинна вимагати «ресурсів дослідження», я думаю, що це є. Хорошими прикладами та дискусіями є: