Переменная ширина и высота 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)