Подтвердить что ты не робот

UICollectionView внутри UITableViewCell - динамическая высота?

Один из наших экранов приложений требует, чтобы мы разместили UICollectionView внутри UITableViewCell. Этот UICollectionView будет иметь динамическое количество элементов, в результате чего высота также должна быть рассчитана динамически. Тем не менее, я столкнулся с проблемами, пытаясь вычислить высоту встроенного UICollectionView.

Наш всеобъемлющий UIViewController был создан в Раскадровки и использует автоматическую компоновку. Но я не знаю, как динамически увеличить высоту UITableViewCell в зависимости от высоты UICollectionView.

Может кто-нибудь дать несколько советов или советов о том, как это сделать?

4b9b3361

Ответ 1

Правильный ответ ДА, вы CAN выполните это.

Я столкнулся с этой проблемой несколько недель назад. На самом деле это проще, чем вы думаете. Поместите свои ячейки в NIB (или раскадровки) и прикрепите их, чтобы авто макет выполнил всю работу.

Учитывая следующую структуру:

TableView

TableViewCell

     
  

CollectionView

         
    

CollectionViewCell

             

CollectionViewCell

             

CollectionViewCell

             

[... переменное число ячеек или разные размеры ячеек]

    

Решение состоит в том, чтобы сообщить авто макету, чтобы сначала вычислить размеры коллекцииViewCell, а затем коллекцию viewSize и использовать ее как размер вашей ячейки. Это метод UIView, который "делает магию":

-(void)systemLayoutSizeFittingSize:(CGSize)targetSize 
     withHorizontalFittingPriority:(UILayoutPriority)horizontalFittingPriority 
           verticalFittingPriority:(UILayoutPriority)verticalFittingPriority

Здесь вы должны указать размер TableViewCell, который в вашем случае представляет собой CollectionView contentSize.

CollectionViewCell

В CollectionViewCell вы должны указывать ячейку в макете каждый раз, когда вы меняете модель (например: вы устанавливаете UILabel с текстом, затем ячейка должна быть макета снова).

- (void)bindWithModel:(id)model {
    // Do whatever you may need to bind with your data and 
    // tell the collection view cell contentView to resize
    [self.contentView setNeedsLayout];
}
// Other stuff here...

TableViewCell

TableViewCell делает магию. Он имеет выход к вашей коллекцииView, позволяет автоматически компоновать ячейки collectionView с помощью оцененногоItemSize UICollectionViewFlowLayout.

Тогда трюк заключается в том, чтобы установить размер ячейки tableView в методе systemLayoutSizeFittingSize.... (ПРИМЕЧАНИЕ: iOS8 или новее)

ПРИМЕЧАНИЕ. Я попытался использовать метод высоты ячейки делегата tableView -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath.but слишком поздно для системы автоматического макета для вычисления CollectionView contentSize, и иногда вы можете найти ячейки с неправильным изменением размера.

@implementation TableCell

- (void)awakeFromNib {
    [super awakeFromNib];
    UICollectionViewFlowLayout *flow = (UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout;

    // Configure the collectionView
    flow.minimumInteritemSpacing = ...;

    // This enables the magic of auto layout. 
    // Setting estimatedItemSize different to CGSizeZero 
    // on flow Layout enables auto layout for collectionView cells.
    // https://developer.apple.com/videos/play/wwdc2014-226/
    flow.estimatedItemSize = CGSizeMake(1, 1);

    // Disable the scroll on your collection view
    // to avoid running into multiple scroll issues.
    [self.collectionView setScrollEnabled:NO];
}

- (void)bindWithModel:(id)model {
    // Do your stuff here to configure the tableViewCell
    // Tell the cell to redraw its contentView        
    [self.contentView layoutIfNeeded];
}

// THIS IS THE MOST IMPORTANT METHOD
//
// This method tells the auto layout
// You cannot calculate the collectionView content size in any other place, 
// because you run into race condition issues.
// NOTE: Works for iOS 8 or later
- (CGSize)systemLayoutSizeFittingSize:(CGSize)targetSize withHorizontalFittingPriority:(UILayoutPriority)horizontalFittingPriority verticalFittingPriority:(UILayoutPriority)verticalFittingPriority {

    // With autolayout enabled on collection view cells we need to force a collection view relayout with the shown size (width)
    self.collectionView.frame = CGRectMake(0, 0, targetSize.width, MAXFLOAT);
    [self.collectionView layoutIfNeeded];

    // If the cell size has to be exactly the content 
    // Size of the collection View, just return the
    // collectionViewLayout collectionViewContentSize.

    return [self.collectionView.collectionViewLayout collectionViewContentSize];
}

// Other stuff here...

@end

TableViewController

Не забудьте включить автоматическую макетную систему для ячеек tableView на вашем TableViewController:

- (void)viewDidLoad {
    [super viewDidLoad];

    // Enable automatic row auto layout calculations        
    self.tableView.rowHeight = UITableViewAutomaticDimension;
    // Set the estimatedRowHeight to a non-0 value to enable auto layout.
    self.tableView.estimatedRowHeight = 10;

}

CREDIT: @rbarbera помог разобраться в этом

Ответ 2

Я думаю, что мое решение намного проще, чем предложенное @PabloRomeu.

Шаг 1. Создайте выход из UICollectionView в UITableViewCell subclass, где находится UICollectionView. Пусть, это имя будет collectionView

Шаг 2. Добавьте IB для ограничения высоты UICollectionView и создайте выход для UITableViewCell subclass. Пусть, это имя будет collectionViewHeight.

Шаг 3. В tableView:cellForRowAtIndexPath: добавьте код:

// deque a cell
cell.frame = tableView.bounds;
[cell layoutIfNeeded];
[cell.collectionView reloadData];
cell.collectionViewHeight.constant = cell.collectionView.collectionViewLayout.collectionViewContentSize.height;

Ответ 3

Оба представления таблиц и представления коллекции являются подклассами UIScrollView и, следовательно, не любят быть встроенными в другое представление прокрутки, поскольку они пытаются вычислить размеры контента, повторно использовать ячейки и т.д.

Я рекомендую вам использовать только просмотр коллекции для всех ваших целей.

Вы можете разделить его по разделам и "рассматривать" макеты некоторых разделов в виде табличного представления, а другие - в виде коллекции. В конце концов, ничего, чего вы не можете достичь с помощью представления коллекции, которое вы можете использовать с табличным представлением.

Если у вас есть базовый макет сетки для "частей" в виде коллекции, вы также можете использовать обычные ячейки таблицы для их обработки. Тем не менее, если вам не нужна поддержка iOS 5, вам лучше использовать представления коллекций.

Ответ 4

Ответ Пабло Ромеу выше (fooobar.com/questions/112835/...) очень помог мне в моей проблеме. Я должен был сделать несколько вещей по-другому, однако, чтобы это работало на мою проблему. Во-первых, мне не нужно было вызывать layoutIfNeeded() так часто. Мне нужно было только вызвать его на collectionView в функции systemLayoutSizeFitting.

Во-вторых, у меня были автоматические ограничения макета для моего представления коллекции в ячейке табличного представления, чтобы дать ему некоторое дополнение. Поэтому мне пришлось вычесть targetSize.width и конечные поля из targetSize.width при установке ширины collectionView.frame. Мне также пришлось добавить верхнее и нижнее поля к возвращаемому значению CGSize height.

Чтобы получить эти константы ограничения, у меня была возможность либо создать выходы для ограничений, либо жестко кодировать их константы, либо найти их по идентификатору. Я решил пойти с третьим вариантом, чтобы сделать мой класс ячеек табличного представления легко повторяемым. В конце концов, это было все, что мне было нужно, чтобы это заработало:

class CollectionTableViewCell: UITableViewCell {

    // MARK: -
    // MARK: Properties
    @IBOutlet weak var collectionView: UICollectionView! {
        didSet {
            collectionViewLayout?.estimatedItemSize = CGSize(width: 1, height: 1)
            selectionStyle = .none
        }
    }

    var collectionViewLayout: UICollectionViewFlowLayout? {
        return collectionView.collectionViewLayout as? UICollectionViewFlowLayout
    }

    // MARK: -
    // MARK: UIView functions
    override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize {
        collectionView.layoutIfNeeded()

        let topConstraintConstant = contentView.constraint(byIdentifier: "topAnchor")?.constant ?? 0
        let bottomConstraintConstant = contentView.constraint(byIdentifier: "bottomAnchor")?.constant ?? 0
        let trailingConstraintConstant = contentView.constraint(byIdentifier: "trailingAnchor")?.constant ?? 0
        let leadingConstraintConstant = contentView.constraint(byIdentifier: "leadingAnchor")?.constant ?? 0

        collectionView.frame = CGRect(x: 0, y: 0, width: targetSize.width - trailingConstraintConstant - leadingConstraintConstant, height: 1)

        let size = collectionView.collectionViewLayout.collectionViewContentSize
        let newSize = CGSize(width: size.width, height: size.height + topConstraintConstant + bottomConstraintConstant)
        return newSize
    }
}

В качестве вспомогательной функции для получения ограничения по идентификатору я добавляю следующее расширение:

extension UIView {
    func constraint(byIdentifier identifier: String) -> NSLayoutConstraint? {
        return constraints.first(where: { $0.identifier == identifier })
    }
}

ПРИМЕЧАНИЕ. Вам нужно будет установить идентификатор для этих ограничений в вашей раскадровке или там, где они создаются. Если у них нет константы 0, тогда это не имеет значения. Также, как и в ответе Пабло, вам нужно будет использовать UICollectionViewFlowLayout в качестве макета для представления вашей коллекции. Наконец, убедитесь, что вы связали collectionView IBOutlet со своей раскадровкой.

С пользовательской ячейкой табличного представления выше, я теперь могу разделить ее на подклассы в любой другой ячейке табличного представления, которая нуждается в представлении коллекции, и заставить его реализовать протоколы UICollectionViewDelegateFlowLayout и UICollectionViewDataSource. Надеюсь, что это полезно для кого-то еще!

Ответ 5

Я бы поставил статический метод в класс представления коллекции, который вернет размер, основанный на содержимом, которое у него будет. Затем используйте этот метод в heightForRowAtIndexPath, чтобы вернуть правильный размер.

Также обратите внимание, что вы можете получить какое-то странное поведение, когда вы вставляете эти типы viewControllers. Я сделал это один раз и имел некоторые странные проблемы с памятью, которые я никогда не разрабатывал.

Ответ 6

Возможно, мой вариант будет полезен; Я решал эту задачу в течение последних двух часов. Я не претендую на 100% правильное или оптимальное, но мое умение очень маленькое, но я бы хотел услышать комментарии экспертов. Спасибо. Одно важное замечание: это работает для статической таблицы - она ​​указана в моей текущей работе. Итак, все, что я использую, viewWillLayoutSubviews из tableView. И немного больше.

private var iconsCellHeight: CGFloat = 500 

func updateTable(table: UITableView, withDuration duration: NSTimeInterval) {
    UIView.animateWithDuration(duration, animations: { () -> Void in
        table.beginUpdates()
        table.endUpdates()
    })
}

override func viewWillLayoutSubviews() {
    if let iconsCell = tableView.cellForRowAtIndexPath(NSIndexPath(forRow: 0, inSection: 1)) as? CategoryCardIconsCell {
        let collectionViewContentHeight = iconsCell.iconsCollectionView.contentSize.height
        if collectionViewContentHeight + 17 != iconsCellHeight {
            iconsCellHeight = collectionViewContentHeight + 17
            updateTable(tableView, withDuration: 0.2)
        }
    }
}

override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    switch (indexPath.section, indexPath.row) {
        case ...
        case (1,0):
            return iconsCellHeight
        default:
            return tableView.rowHeight
    }
}
  • Я знаю, что коллекцияView находится в первой строке второго раздела;
  • Пусть высота строки равна 17 p. больше, чем его высота содержимого;
  • iconsCellHeight - это случайное число при запуске программы (я знаю, что в портретной форме это должно быть ровно 392, но это не важно). Если содержимое коллекцииView + 17 не равно этому числу, измените его значение. В следующий раз в этой ситуации условие дает FALSE;
  • После обновления tableView. В моем случае это комбинация двух операций (для приятного обновления расширяющихся строк);
  • И, конечно, в heightForRowAtIndexPath добавьте одну строку для кода.

Ответ 7

Альтернативой решению Pablo Romeu является настройка самого UICollectionView, а не выполнение работы в ячейке таблицы.

Основная проблема заключается в том, что по умолчанию представление коллекции не имеет собственного размера и поэтому не может сообщить автоматическое расположение используемых размеров. Вы можете исправить это, создав собственный подкласс, который возвращает полезный внутренний размер.

Создайте подкласс UICollectionView и переопределите следующие методы

override func intrinsicContentSize() -> CGSize {
    self.layoutIfNeeded()

    var size = super.contentSize
    if size.width == 0 || size.height == 0 {
        // return a default size
        size = CGSize(width: 600, height:44)
    }

    return size
 }

override func reloadData() {
    super.reloadData()
    self.layoutIfNeeded()
    self.invalidateIntrinsicContentSize()
}

(Вы также должны переопределить связанные методы: reloadSections, reloadItemsAtIndexPaths аналогично reloadData())

Вызов layoutIfNeeded заставляет представление коллекции пересчитывать размер содержимого, который затем может использоваться как новый собственный размер.

Кроме того, вам нужно явно обрабатывать изменения размера представления (например, при вращении устройства) в контроллере табличного представления

    override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator)
{
    super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
    dispatch_async(dispatch_get_main_queue()) {
        self.tableView.reloadData()
    }
}

Ответ 8

Самый простой подход, который я когда-либо придумал, Кредиты на @igor, ответ выше,

В вашем классе tableviewcell просто вставьте это

override func layoutSubviews() {
    self.collectionViewOutlet.constant = self.postPoll.collectionViewLayout.collectionViewContentSize.height
}

и, конечно, измените коллекцию viewoutlet с вашей розеткой в классе ячеек

Ответ 9

Недавно я столкнулся с той же проблемой, и я почти попробовал каждое решение в ответах, некоторые из них работали, а другие не интересовались моим подходом к @PabloRomeu: если у вас есть другое содержимое в ячейке (кроме представления коллекции) вам нужно будет рассчитать их высоту и высоту их ограничений и вернуть результат, чтобы получить правильное автоматическое расположение, и я не люблю вычислять вещи вручную в своем коде. Итак, вот решение, которое отлично сработало для меня без каких-либо ручных вычислений в моем коде.

в cellForRow:atIndexPath табличного представления я делаю следующее:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    //do dequeue stuff
    //initialize the the collection view data source with the data
    cell.frame = CGRect.zero
    cell.layoutIfNeeded()
    return cell
}

Я думаю, что здесь происходит то, что я заставляю ячейку табличного представления корректировать ее высоту после того, как высота представления коллекции была вычислена. (после предоставления даты collectionView источнику данных)

Ответ 10

Я прочитал все ответы. Это, кажется, служит всем случаям.

override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize {
        collectionView.layoutIfNeeded()
        collectionView.frame = CGRect(x: 0, y: 0, width: targetSize.width , height: 1)
        return collectionView.collectionViewLayout.collectionViewContentSize
}

Ответ 11

Я получаю идею из поста @Igor и вкладываю в это свое время для своего проекта с помощью swift

Просто мимо этого в вашем

 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    //do dequeue stuff
    cell.frame = tableView.bounds
    cell.layoutIfNeeded()
    cell.collectionView.reloadData()
    cell.collectionView.heightAnchor.constraint(equalToConstant: cell.collectionView.collectionViewLayout.collectionViewContentSize.height)
    cell.layoutIfNeeded()
    return cell
}

Дополнение: Если вы видите, что ваш UICollectionView прерывистый при загрузке ячеек.

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {       
  //do dequeue stuff
  cell.layer.shouldRasterize = true
  cell.layer.rasterizationScale = UIScreen.main.scale
  return cell
}

Ответ 12

Решение Pablo не очень хорошо для меня работало, у меня были странные визуальные эффекты (коллекция неправильно корректировалась).

То, что работало, состояло в том, чтобы приспособить ограничение высоты collectionView (как NSLayoutConstraint) к collectionView contentSize во время layoutSubviews(). Этот метод вызывается, когда autolayout применяется к ячейке.

// Constraint on the collectionView height in the storyboard. Priority set to 999.
@IBOutlet weak var collectionViewHeightConstraint: NSLayoutConstraint!


// Method called by autolayout to layout the subviews (including the collectionView).
// This is triggered with 'layoutIfNeeded()', or by the viewController
// (happens between 'viewWillLayoutSubviews()' and 'viewDidLayoutSubviews()'.
override func layoutSubviews() {

    collectionViewHeightConstraint.constant = collectionView.contentSize.height
    super.layoutSubviews()
}

// Call 'layoutIfNeeded()' when you update your UI from the model to trigger 'layoutSubviews()'
private func updateUI() {
    layoutIfNeeded()
}

Ответ 13

В UITableViewDelegate:

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return ceil(itemCount/4.0f)*collectionViewCellHeight;
}

Замените itemCount и CollectionViewCellHeight на реальные значения. Если у вас массив массивов itemCount может быть:

self.items[indexPath.row].count

Или что угодно.

Ответ 14

1. Создайте фиктивную ячейку.
2. Используйте метод collectionViewContentSize для UICollectionViewLayout UICollectionView с использованием текущих данных.

Ответ 15

Вы можете рассчитать высоту коллекции на основе ее свойств, таких как itemSize, sectionInset, minimumLineSpacing, minimumInteritemSpacing, если ваш CollectionViewCell имеет границу правила.