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.

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.


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

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];

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

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
    }

}

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

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

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.


@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)

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

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


0

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

let height = collectionView.collectionViewLayout.collectionViewContentSize.height

0

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

theCollectionView.collectionViewLayout.collectionViewContentSize;
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.