Похоже, что вы просите об использовании 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 в «Advanced Collection View».)
Пара замечаний. Он использует layoutIfNeeded () для принудительного макета всей ячейки, чтобы вычислить и установить ширину метки. Но этого мало. Я считаю, что вам также нужно настроить preferredMaxLayoutWidth
так, чтобы метка использовала эту ширину с Auto Layout. И только тогда вы можете использовать systemLayoutSizeFittingSize
, чтобы ячейка вычисляла свою высоту с учетом метки.
Нравится ли мне такой подход? Нет !! Он кажется слишком сложным, и он выполняет макет дважды. Но пока производительность не становится проблемой, я бы предпочел выполнить макет дважды во время выполнения, чем дважды определять его в коде, что, по-видимому, является единственной альтернативой.
Я надеюсь, что со временем саморазмерные ячейки будут работать по-другому, и все это станет намного проще.
Пример проекта, показывающий это в работе.
Но почему бы просто не использовать саморазмерные ячейки?
Теоретически новые возможности iOS8 для «самоизмеривания ячеек» должны сделать это ненужным. Если вы определили ячейку с помощью Auto Layout (AL), то представление коллекции должно быть достаточно умным, чтобы позволить ей размер и правильную компоновку. На практике я не встречал примеров, в которых бы это работало с многострочными метками. Я думаю, это отчасти потому, что механизм самоизмерительной ячейки все еще не работает.
Но я готов поспорить, что это в основном из-за обычной сложности Auto Layout и меток, которая заключается в том, что UILabels требует в основном двухэтапного процесса макета. Мне непонятно, как можно выполнить оба шага с самоизмеряющимися ячейками.
И, как я уже сказал, это действительно работа для другого макета. Часть сущности потокового макета состоит в том, что он позиционирует элементы, имеющие размер, а не фиксирует ширину и позволяет им выбирать высоту.
А что насчет предпочтительныхLayoutAttributesFittingAttributes:?
preferredLayoutAttributesFittingAttributes:
Я считаю, что этот метод - отвлекающий маневр. Это только то, что нужно использовать с новым механизмом самоизмерительной ячейки. Так что это не ответ, если этот механизм ненадежен.
А что случилось с systemlayoutSizeFittingSize :?
Вы правы, документы сбивают с толку.
В документации systemLayoutSizeFittingSize:
и в systemLayoutSizeFittingSize:withHorizontalFittingPriority:verticalFittingPriority:
том, и в другом предполагается, что вы должны передавать только UILayoutFittingCompressedSize
и UILayoutFittingExpandedSize
в качестве targetSize
. Однако сама подпись метода, комментарии заголовка и поведение функций указывают на то, что они реагируют на точное значение targetSize
параметра.
Фактически, если вы установите UICollectionViewFlowLayoutDelegate.estimatedItemSize
, чтобы включить новый механизм саморазмерной ячейки, это значение, похоже, будет передано как targetSize. И, UILabel.systemLayoutSizeFittingSize
похоже, возвращает те же значения, что и UILabel.sizeThatFits
. Это подозрительно, учитывая, что аргумент to systemLayoutSizeFittingSize
должен быть приблизительной целью, а аргумент sizeThatFits:
должен быть максимальным ограничивающим размером.
Дополнительные ресурсы
Хотя грустно думать, что такое рутинное требование требует «исследовательских ресурсов», я думаю, что это так. Хорошие примеры и обсуждения: