Как определить высоту UICollectionView с помощью FlowLayout

118

У меня есть UICollectionViewс UICollectionViewFlowLayout, и я хочу , чтобы вычислить его содержание размера (для возвращения в intrinsicContentSizeнеобходимости для регулировки его высоты с помощью AutoLayout).

Проблема заключается в следующем: даже если у меня есть фиксированная и равная высота для всех ячеек, я не знаю, сколько «строк» ​​/ строк у меня есть в UICollectionView. Я также не могу определить это количество по количеству элементов в моем источнике данных, поскольку ячейки, представляющие элементы данных, различаются по ширине, следовательно, и количество элементов в одной строке файла UICollectionView.

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

Игнатий Тремор
источник

Ответы:

254

Вау! По какой-то причине после нескольких часов исследований я нашел довольно простой ответ на свой вопрос: я полностью искал не в том месте, копаясь во всей документации, которую мог найти UICollectionView.

Простое и легкое решение заключается в базовом макете: просто вызовите collectionViewContentSizeсвое myCollectionView.collectionViewLayoutсвойство, и вы получите высоту и ширину содержимого как CGSize. Это так просто.

Игнатий Тремор
источник
1
Ого, я бы хотел, чтобы в UITableView было что-то подобное
Крис Чен,
1
Игнатус, @jbandi, Крис и другие, вы можете подробнее рассказать об этом? Когда я тестировал горизонтальное направление прокрутки с большим интервалом между элементами (чтобы элементы располагались горизонтально), представление коллекции возвращало недопустимый внутренний размер содержимого (-1, -1), а collectionViewContentSize возвращал то, что было эффективно границы представления коллекции - не считая того, что была только одна строка, окруженная пробелом. Короче говоря, это было не очень присуще. Спасибо!
Крис Коновер
8
В чем разница между collectionViewContentSize и contentSize в представлении прокрутки?
rounak 06
Когда вы вызываете contentSize представления коллекции в viewDidLoad, он возвращает фрейм представления коллекции, collectionViewContentSize возвращает правильный размер содержимого.
Fury
1
Для кого это может помочь: перед этим мне нужно было выполнить layoutIfNeeded (), чтобы он заработал.
asanli 05
37

Если вы используете автоматический макет , вы можете создать подклассUICollectionView

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

Ниже приводится реализация:

@interface DynamicCollectionView : UICollectionView

@end

@implementation DynamicCollectionView

- (void) layoutSubviews
{
    [super layoutSubviews];

    if (!CGSizeEqualToSize(self.bounds.size, [self intrinsicContentSize]))
    {
        [self invalidateIntrinsicContentSize];
    }
}

- (CGSize)intrinsicContentSize
{
    CGSize intrinsicContentSize = self.contentSize;

    return intrinsicContentSize;
}

@end
user1046037
источник
12
не сработало для меня ... Я использую свой CollectionView внутри UITableViewCell и хотел бы, чтобы табличное представление увеличивалось в высоту по мере роста представления коллекции с использованием iOS8 UITableViewAutomaticDimension. Но мое представление коллекции остается небольшим :(
Георг
Потрясающие. Короткое и приятное решение для моего UICollectionView внутри UIScrollView!
dotToString
2
Одна важная вещь - отключить прокрутку и полосы прокрутки в представлении коллекции
Георг
1
Та же проблема, что и у Георга, это около 50 пикселей в высоту, тогда как должно быть больше 400
xtrinch
3
Да, я установил для представления коллекции значение «без прокрутки, без полос прокрутки», а затем перезагружаю его, после чего устанавливается содержимое представления таблицы. Также collectionview является подклассом, который возвращает размер содержимого как intrinsicContentSize. Надеюсь, это поможет!
Георг
25

В viewDidAppearвы можете получить его:

float height = self.myCollectionView.collectionViewLayout.collectionViewContentSize.height;

Возможно, когда вы перезагружаете данные, вам нужно рассчитать новую высоту с новыми данными, тогда вы можете получить это: добавьте наблюдателя, который будет слушать, когда вы CollectionViewзакончите перезагрузку данных по адресу viewdidload:

[self.myCollectionView addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionOld context:NULL];

Затем добавьте функцию ниже, чтобы получить новую высоту или сделать что-нибудь после перезагрузки коллекции:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary  *)change context:(void *)context
{
    //Whatever you do here when the reloadData finished
    float newHeight = self.myCollectionView.collectionViewLayout.collectionViewContentSize.height;    
}

И не забудьте удалить наблюдателя:

[self.myCollectionView removeObserver:self forKeyPath:@"contentSize" context:NULL];
подветренный
источник
11

user1046037 ответ в Swift ...

class DynamicCollectionView: UICollectionView {
    override func layoutSubviews() {
        super.layoutSubviews()
        if bounds.size != intrinsicContentSize() {
            invalidateIntrinsicContentSize()
        }
    }

    override func intrinsicContentSize() -> CGSize {
        return self.contentSize
    }
}
Ник Вуд
источник
Это работает отлично, но если у вас есть что-то вроде UILabelвыше, контент из collectionViewдругого элемента выше, у вас есть другое решение?
KolaCaine
11

Swift 3 код для пользователя 1046037 ответ

import UIKit

class DynamicCollectionView: UICollectionView {

    override func layoutSubviews() {
        super.layoutSubviews()
        if !__CGSizeEqualToSize(bounds.size, self.intrinsicContentSize) {
            self.invalidateIntrinsicContentSize()
        }

    }

    override var intrinsicContentSize: CGSize {
        return contentSize
    }

}
Мохаммад Садик Шейх
источник
Я новее, и я не уверен, где и как использовать это в моем ViewController. Не могли бы вы отредактировать ответ ...?
Абирами Бала
1
просто установите этот класс в свой просмотр коллекции в Раскадровке
Мохаммад Садик Шейх
7

Swift 4

class DynamicCollectionView: UICollectionView {
  override func layoutSubviews() {
    super.layoutSubviews()
    if !__CGSizeEqualToSize(bounds.size, self.intrinsicContentSize) {
      self.invalidateIntrinsicContentSize()
    }
  }

  override var intrinsicContentSize: CGSize {
    return collectionViewLayout.collectionViewContentSize
  }
}
Wilson
источник
1
Я обнаружил, что он не работает в iPhone 6/7 plus и XR, XS-MAX
Рахул К. Раджан
7

Swift 4.2

class DynamicCollectionView: UICollectionView {
    override func layoutSubviews() {
        super.layoutSubviews()
        if bounds.size != intrinsicContentSize {
            invalidateIntrinsicContentSize()
        }
    }

    override var intrinsicContentSize: CGSize {
        return self.contentSize
    }
}
Чезаре
источник
4

Слишком поздно отвечать на этот вопрос, но я недавно столкнулся с этой проблемой. Ответ
@ lee выше очень помог мне получить ответ. Но этот ответ ограничен objective-c, и я работал быстро .
@lee Что касается вашего ответа, позвольте мне позволить быстрому пользователю легко решить эту проблему. Для этого выполните следующие действия:

Объявите CGFloatпеременную в разделе объявлений:

var height : CGFloat!

В viewDidAppearвы можете получить его:

height = self.myCollectionView.collectionViewLayout.collectionViewContentSize().height

Возможно, когда вам reloadнужно вычислить новую высоту с новыми данными, вы можете получить это: addObserverчтобы прослушать, когда ваши CollectionViewзаконченные данные перезагрузки будут viewWillAppear:

override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(true)
        ....
        ....
        self.shapeCollectionView.addObserver(self, forKeyPath: "contentSize", options: NSKeyValueObservingOptions.Old, context: nil)
    }

Затем добавьте функцию ниже, чтобы получить новую высоту или сделать что-нибудь после collectionviewперезагрузки:

override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
        let newHeight : CGFloat = self.myCollectionView.collectionViewLayout.collectionViewContentSize().height

        var frame : CGRect! = self.myCollectionView.frame
        frame.size.height = newHeight

        self.myCollectionView.frame = frame
    }

И не забудьте удалить наблюдателя:

override func viewWillDisappear(animated: Bool) {
    super.viewWillDisappear(true)
    ....
    ....
    self.myCollectionView.removeObserver(self, forKeyPath: "contentSize")
}

Надеюсь, это поможет вам быстро решить вашу проблему.

Er. Вихар
источник
@ShikhaSharma: вы можете добавить код removeObserver в метод ViewDidDisappear, который удалит наблюдателя только после того, как представление подтвердит, что он исчез. Пожалуйста, проверьте обновленный ответ.
Эээ. Vihar
я получаю эту ошибку: An -observeValueForKeyPath: ofObject: change: context: сообщение было получено, но не обработано.
Шиха Шарма
@ShikhaSharma: Извините, это моя ошибка указывать метод. Попробуйте поместить код removeobserver в viewWillDisappear.
Эээ. Vihar
1

Этот ответ основан на @Er. Ответ Вихара. Вы можете легко реализовать решение с помощью RxSwift

     collectionView.rx.observe(CGSize.self , "contentSize").subscribe(onNext: { (size) in
        print("sizer \(size)")
    }).disposed(by: rx.disposeBag)
HUNG TRAN DUY
источник
0

Быстрая версия @ user1046037:

public class DynamicCollectionView: UICollectionView {

    override public func layoutSubviews() {
        super.layoutSubviews()
        if !bounds.size.equalTo(intrinsicContentSize) {
            invalidateIntrinsicContentSize()
        }
    }

    override public var intrinsicContentSize: CGSize {
        let intrinsicContentSize: CGSize = contentSize
        return intrinsicContentSize
    }
}
Джошуа Харт
источник
0

Swift 4.2, Xcode 10.1, iOS 12.1:

По какой-то причине collectionView.contentSize.heightон казался меньше разрешенной высоты моего представления коллекции. Во-первых, я использовал ограничение автоматической компоновки относительно 1/2 высоты супервизора.Чтобы исправить это, я изменил ограничение, чтобы оно относилось к «безопасной области» представления.

Это позволило мне установить высоту ячейки, чтобы заполнить представление моей коллекции, используя collectionView.contentSize.height:

private func setCellSize() {
    let height: CGFloat = (collectionView.contentSize.height) / CGFloat(numberOfRows)
    let width: CGFloat = view.frame.width - CGFloat(horizontalCellMargin * 2)

    let layout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
    layout.itemSize = CGSize(width: width, height: height)
}

Перед

ограничение высоты относительно супервизора плохой результат симулятора

После

ограничение по высоте относительно безопасной зоны хороший результат на симуляторе

Эндрю Кирна
источник
0

предполагая, что ваш collectionView назван как collectionView, вы можете выбрать высоту следующим образом.

let height = collectionView.collectionViewLayout.collectionViewContentSize.height
Прамодья Абейсингхе
источник
0

Вдобавок лучше позвонить, reloadDataпрежде чем набрать высоту:

theCollectionView.collectionViewLayout.collectionViewContentSize;
guozqzzu
источник