Переменная ширина и высота UICollectionViewCell с использованием AutoLayout со Storyboard (без XIB)
Вот проблема дизайна в приложении, использующая AutoLayout, UICollectionView и UICollectionViewCell, которая автоматически изменяет размер и высоту в зависимости от ограничений AutoLayout и его содержимого (некоторый текст).
Это список UITableView, например, с каждой ячейкой, которая имеет собственную ширину и высоту, вычисленную отдельно для каждой строки, зависящей от ее содержимого. Это больше похоже на создание iOS-сообщений в приложении (или WhatsUp).
Очевидно, что приложение должно использовать func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize
.
Проблема заключается в том, что в этом методе приложение не может вызывать func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
и dequeueReusableCellWithReuseIdentifier(identifier: String, forIndexPath indexPath: NSIndexPath!) -> AnyObject
для создания экземпляра ячейки, заполнения ее конкретным контентом и вычисления его ширины и высоты. Попытка сделать это приведет к бессрочным вызовам рекурсии или к потере какого-либо другого типа (по крайней мере, в iOS 8.3).
Ближайший способ исправить эту ситуацию, похоже, скопирует определение ячейки в иерархию представлений, чтобы автоматическая компоновка автоматически изменяла размер ячейки (например, ячейка имела ту же ширину, что и представление родительской коллекции), поэтому приложение может конфигурировать ячейку с конкретный контент и рассчитать его размер. Это определенно не единственный способ исправить это из-за дублированных ресурсов.
Все это связано с настройкой UILabel.preferredMaxLayoutWidth на какое-то значение, которое должно быть автоматическим макетом (не жестко запрограммированным), который может зависеть от ширины и высоты экрана или, по крайней мере, с помощью определения ограничения автомакета, поэтому приложение может получить вычисляется многострочный внутренний размер UILabel.
Я бы не хотел создавать экземпляры ячеек из XIB файла, так как Storyboards должны быть сегодня отраслевым стандартом, и я хотел бы иметь меньшее вмешательство в код.
EDIT:
Базовый код, который не может быть запущен, приведен ниже. Таким образом, только экземпляр переменной cellProbe (не используется) приводит к сбою приложения. Без этого вызова приложение работает плавно.
var onceToken: dispatch_once_t = 0
class ViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
dispatch_once(&onceToken) {
let cellProbe = collectionView.dequeueReusableCellWithReuseIdentifier("first", forIndexPath: NSIndexPath(forRow: 0, inSection: 0)) as! UICollectionViewCell
}
return CGSize(width: 200,height: 50)
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("first", forIndexPath: NSIndexPath(forRow: 0, inSection: 0)) as! UICollectionViewCell
return cell
}
}
Ответы
Ответ 1
Если вы используете ограничения, вам не нужно устанавливать размер на ячейку. Поэтому вы должны удалить свой метод делегата func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize
Вместо этого вам нужно установить размер в estimatedItemSize
, установив для этого значение, отличное от CGSizeZero
, которое вы указали макет, который еще не знает точного размера. Затем макет будет запрашивать каждую ячейку для этого размера, и он должен быть рассчитан, когда это необходимо.
let layout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
layout.estimatedItemSize = someReasonableEstimatedSize()
Ответ 2
Точно так же, как при динамическом увеличении размера таблицы в виде таблицы с использованием автоматической компоновки вам не нужен вызов
func tableView(_ tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
в
optional func tableView(_ tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat
правильный способ - создать локальную статическую ячейку для вычисления высоты, например метод класса пользовательской ячейки
+ (CGFloat)cellHeightForContent:(id)content tableView:(UITableView *)tableView {
static CustomCell *cell;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
cell = [tableView dequeueReusableCellWithIdentifier:[CustomCell cellIdentifier]];
});
Item *item = (Item *)content;
configureBasicCell(cell, item);
[cell setNeedsLayout];
[cell layoutIfNeeded];
CGSize size = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
return size.height + 1.0f; // Add 1.0f for the cell separator height
}
и я серьезно сомневаюсь, что раскадровка - это какой-то отраслевой стандарт. xib - лучший способ создать пользовательский элемент, такой как view, включая tableViewCell и CollectionViewCell
Ответ 3
Чтобы вычислить размер ячейки CollectionView
динамически, просто установите свойство макета потока для любого произвольного значения:
let flowLayout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
flowLayout.estimatedItemSize = CGSize(width: 0, height: 0)