Como determinar a altura de UICollectionView com FlowLayout

118

Eu tenho um UICollectionViewcom um UICollectionViewFlowLayoute quero calcular seu tamanho de conteúdo (para retorno é intrinsicContentSizenecessário ajustar sua altura via AutoLayout).

O problema é: Mesmo que eu tenha uma altura fixa e igual para todas as células, não sei quantas "linhas" / linhas tenho no UICollectionView. Também não posso determinar essa contagem pelo número de itens em minha fonte de dados, uma vez que as células que representam os itens de dados variam em largura, o mesmo acontece com o número de itens que tenho em uma linha do UICollectionView.

Como não consegui encontrar nenhuma dica sobre este tópico na documentação oficial e o Google não me trouxe mais nada, qualquer ajuda e ideias seriam muito apreciadas.

Inácio Tremor
fonte

Respostas:

255

Uau! Por alguma razão, depois de horas de pesquisa, agora encontrei uma resposta bem fácil para minha pergunta: eu estava completamente pesquisando no lugar errado, vasculhando toda a documentação que pude encontrar UICollectionView.

A solução simples e fácil está no layout subjacente: basta chamar collectionViewContentSizesua myCollectionView.collectionViewLayoutpropriedade e você obterá a altura e largura do conteúdo como CGSize. É tão fácil quanto isso.

Inácio Tremor
fonte
1
uau, gostaria que o UITableView tivesse algo semelhante
Chris Chen
1
Ignatus, @jbandi, Chris et al, você pode expandir isso? Quando testei com uma direção de rolagem horizontal com um grande espaçamento entre itens (de modo que os itens fossem dispostos horizontalmente), a exibição da coleção retornou um tamanho de conteúdo intrínseco inválido (-1, -1) e collectionViewContentSize retornou o que era efetivamente os limites da visualização da coleção - sem levar em conta que havia apenas uma linha cercada por espaço. Em suma, não era muito intrínseco. Obrigado!
Chris Conover
8
Qual é a diferença entre collectionViewContentSize e contentSize da visualização de rolagem?
rounak de
Quando você chama contentSize da visualização da coleção em viewDidLoad, ele retorna o quadro da visualização da coleção, collectionViewContentSize retorna o tamanho correto do conteúdo.
Fury
1
Para quem pode ajudar: Tive que fazer um "layoutIfNeeded ()" antes para fazer funcionar.
asanli
37

Caso você esteja usando o layout automático , você pode criar uma subclasse deUICollectionView

Se você usar o código abaixo, não será necessário especificar nenhuma restrição de altura para a visualização da coleção, pois ela varia com base no conteúdo da visualização da coleção.

A seguir está a implementação:

@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
fonte
12
não funcionou para mim ... Estou usando meu CollectionView dentro de um UITableViewCell e gostaria que o tableview aumentasse de altura à medida que a visualização da coleção aumentasse usando iOS8 UITableViewAutomaticDimension Mas minha visualização da coleção permanece pequena :(
Georg
Impressionante. Solução curta e doce para meu UICollectionView dentro de um UIScrollView!
dotToString
2
Uma coisa importante é desativar a rolagem e as barras de rolagem na visualização da coleção
Georg
1
O mesmo problema que Georg, tem cerca de 50 px de altura quando deveria ter mais de 400
xtrinch de
3
Sim, eu defino a visualização da coleção como "not scrollabe, no scollbars" e, em seguida, recarrego-a e o conteúdo da tableview é definido. Além disso, a visão da coleção é uma subclasse que retorna o tamanho do conteúdo como intrinsicContentSize. Espero que ajude!
Georg
25

Em viewDidAppearvocê pode obtê-lo por:

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

Talvez quando você recarregar dados e precisar calcular uma nova altura com novos dados, você pode obtê-lo: adicione o observador para ouvir quando seus CollectionViewdados de recarga terminarem em viewdidload:

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

Em seguida, adicione a função abaixo para obter uma nova altura ou faça qualquer coisa após a coleção de visualização terminar de recarregar:

- (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;    
}

E não se esqueça de remover o observador:

[self.myCollectionView removeObserver:self forKeyPath:@"contentSize" context:NULL];
Lee
fonte
11

user1046037 responder em Swift ...

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

    override func intrinsicContentSize() -> CGSize {
        return self.contentSize
    }
}
Nick Wood
fonte
Funciona perfeitamente, mas se você tem algo como o UILabelacima, o conteúdo collectionViewpassa por cima de outro elemento acima, você tem outra solução?
KolaCaine de
11

Código Swift 3 para a resposta do usuário 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
    }

}
Mohammad Sadiq Shaikh
fonte
Eu sou um calouro e não tenho certeza sobre onde e como usar isso no meu ViewController. Você pode fazer uma edição com a resposta ...?
Abirami Bala
1
apenas defina esta classe para sua visão de coleção no Storyboard
Mohammad Sadiq Shaikh
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
fonte
1
Descobri que não está funcionando no iPhone 6/7 plus e XR, XS-MAX
Rahul K Rajan
7

Swift 4.2

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

    override var intrinsicContentSize: CGSize {
        return self.contentSize
    }
}
Cesare
fonte
4

É tarde demais para responder a essa pergunta, mas recentemente resolvi esse problema.
A resposta de @ Lee acima me ajudou muito a obter minha resposta. Mas essa resposta é limitada ao objetivo-c e eu estava trabalhando rapidamente .
@lee Com referência à sua resposta, permita-me deixar o rápido usuário resolver esse problema facilmente. Para isso, siga os passos abaixo:

Declare uma CGFloatvariável na seção de declaração:

var height : CGFloat!

Em viewDidAppearvocê pode obtê-lo por:

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

Talvez quando seus reloaddados precisarem calcular uma nova altura com novos dados, você pode obtê-los: addObserverpara ouvir quando seus CollectionViewdados de recarregamento terminados em viewWillAppear:

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

Em seguida, adicione a função abaixo para obter uma nova altura ou faça qualquer coisa após collectionviewterminar a recarga:

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
    }

E não se esqueça de remover o observador:

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

Espero que isso ajude você a resolver seu problema rapidamente.

Er. Vihar
fonte
@ShikhaSharma: Você pode adicionar o código removeObserver dentro do método "ViewDidDisappear", que removerá o observador somente após a visualização confirmar que ele desapareceu. Verifique a resposta atualizada.
Er. Vihar
Estou recebendo este erro: Um -observeValueForKeyPath: ofObject: change: context: a mensagem foi recebida, mas não tratada.
Shikha Sharma
@ShikhaSharma: Desculpe, é meu erro especificar o método. Tente colocar o código removeobserver em viewWillDisappear.
Er. Vihar
1

Esta resposta é baseada em @Er. A resposta de Vihar. Você pode facilmente implementar a solução usando RxSwift

     collectionView.rx.observe(CGSize.self , "contentSize").subscribe(onNext: { (size) in
        print("sizer \(size)")
    }).disposed(by: rx.disposeBag)
HUNG TRAN DUY
fonte
0

Versão Swift de @ 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
    }
}
Joshua Hart
fonte
0

Swift 4.2, Xcode 10.1, iOS 12.1:

Por algum motivo, collectionView.contentSize.heightestava parecendo menor do que a altura resolvida da visualização da minha coleção. Primeiro, eu estava usando uma restrição de layout automático relativa a 1/2 da altura do superview. Para corrigir isso, alterei a restrição para ser relativa à "área segura" da visualização.

Isso me permitiu definir a altura da célula para preencher a visualização da minha coleção usando 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)
}

Antes

restrição de altura em relação ao superview resultado ruim do simulador

Depois de

restrição de altura em relação à área segura bom resultado do simulador

Andrew Kirna
fonte
0

presumindo que sua collectionView seja nomeada como collectionView, você pode obter a altura da seguinte maneira.

let height = collectionView.collectionViewLayout.collectionViewContentSize.height
Pramodya Abeysinghe
fonte
0

Além disso, é melhor você ligar reloadDataantes de obter a altura:

theCollectionView.collectionViewLayout.collectionViewContentSize;
guozqzzu
fonte