Células de dimensionamento automático UICollectionView com layout automático

207

Estou tentando obter o dimensionamento UICollectionViewCellsautomático trabalhando com o Layout automático, mas não consigo fazer com que as células se dimensionem de acordo com o conteúdo. Estou tendo problemas para entender como o tamanho da célula é atualizado a partir do conteúdo do conteúdo da célula.

Aqui está a configuração que eu tentei:

  • Personalizado UICollectionViewCellcom um UITextViewno seu contentView.
  • A rolagem para UITextViewestá desativada.
  • A restrição horizontal do contentView é: "H: | [_textView (320)]", ou seja, UITextViewé fixada à esquerda da célula com uma largura explícita de 320.
  • A restrição vertical do contentView é: "V: | -0 - [_ textView]", ou seja, UITextViewfixada na parte superior da célula.
  • A UITextViewrestrição de altura é definida como uma constante na qual os UITextViewrelatórios cabem no texto.

Aqui está o que parece com o fundo da célula definido como vermelho e o UITextViewfundo definido como azul: fundo de célula vermelho, UITextView fundo azul

Coloquei o projeto com o qual estou jogando no GitHub aqui .

rawbee
fonte
Você está implementando o método sizeForItemAtIndexPath?
Daniel Galasko
@DanielGalasko Eu não sou. Meu entendimento é que o novo recurso de células de dimensionamento automático no iOS 8 não exige mais que você faça isso.
rawbee
Não sei de onde você obteve essas informações, mas ainda precisa, dê uma olhada no trecho da WWDC asciiwwdc.com/2014/sessions/226 #
Daniel Galasko
mas novamente isso depende de seu caso de uso, certifique-se de implementar estimatedItemSize
Daniel Galasko
1
2019 - É essencial olhar para isto: stackoverflow.com/a/58108897/294884
Fattie

Respostas:

309

Atualizado para o Swift 5

preferredLayoutAttributesFittingAttributesrenomeado para preferredLayoutAttributesFittinge usar o dimensionamento automático


Atualizado para o Swift 4

systemLayoutSizeFittingSize renomeado para systemLayoutSizeFitting


Atualizado para iOS 9

Depois de ver minha solução GitHub quebrar no iOS 9, finalmente tive tempo de investigar o problema completamente. Atualizei agora o repositório para incluir vários exemplos de configurações diferentes para células com dimensionamento automático. Minha conclusão é que as células de auto-dimensionamento são ótimas em teoria, mas confusas na prática. Uma palavra de cautela ao prosseguir com células de auto-dimensionamento.

TL; DR

Confira meu projeto do GitHub


As células de auto-dimensionamento são suportadas apenas com o layout de fluxo, portanto, verifique se é isso que você está usando.

Há duas coisas que você precisa configurar para que as células com auto-dimensionamento funcionem.

1. Set estimatedItemSizeonUICollectionViewFlowLayout

O layout do fluxo se tornará dinâmico por natureza quando você definir a estimatedItemSizepropriedade.

self.flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

2. Adicione suporte para dimensionamento na subclasse da sua célula

Isso vem em 2 sabores; Layout automático ou substituição personalizada de preferredLayoutAttributesFittingAttributes.

Crie e configure células com o Layout automático

Não vou entrar em detalhes sobre isso, pois há uma brilhante publicação de SO sobre a configuração de restrições para uma célula. Apenas tenha cuidado com o fato de o Xcode 6 ter quebrado um monte de coisas com o iOS 7, portanto, se você suporta o iOS 7, precisará fazer coisas como garantir que a autoresizingMask seja definida no contentView da célula e que os limites do contentView sejam definidos como os limites da célula quando a célula está carregada (ou seja awakeFromNib).

O que você precisa saber é que sua célula precisa ser mais seriamente restringida do que uma célula de exibição de tabela. Por exemplo, se você deseja que sua largura seja dinâmica, sua célula precisa de uma restrição de altura. Da mesma forma, se você deseja que a altura seja dinâmica, precisará de uma restrição de largura para sua célula.

Implemente preferredLayoutAttributesFittingAttributesem sua célula personalizada

Quando essa função é chamada, sua visualização já foi configurada com conteúdo (ou seja cellForItem, foi chamada). Supondo que suas restrições foram definidas adequadamente, você pode ter uma implementação como esta:

//forces the system to do one layout pass
var isHeightCalculated: Bool = false

override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
    //Exhibit A - We need to cache our calculation to prevent a crash.
    if !isHeightCalculated {
        setNeedsLayout()
        layoutIfNeeded()
        let size = contentView.systemLayoutSizeFitting(layoutAttributes.size)
        var newFrame = layoutAttributes.frame
        newFrame.size.width = CGFloat(ceilf(Float(size.width)))
        layoutAttributes.frame = newFrame
        isHeightCalculated = true
    }
    return layoutAttributes
}

NOTA No iOS 9, o comportamento mudou um pouco, o que poderia causar falhas na sua implementação, se você não tomar cuidado (veja mais aqui ). Ao implementar, preferredLayoutAttributesFittingAttributesvocê precisa garantir que você altere apenas o quadro dos atributos de layout uma vez. Se você não fizer isso, o layout chamará sua implementação indefinidamente e, eventualmente, falhará. Uma solução é armazenar em cache o tamanho calculado em sua célula e invalidá-lo sempre que você reutilizar a célula ou alterar seu conteúdo, como fiz com a isHeightCalculatedpropriedade

Experimente seu layout

Nesse ponto, você deve ter células dinâmicas 'funcionando' em seu collectionView. Ainda não achei a solução pronta para uso suficiente durante meus testes, portanto, fique à vontade para comentar se você tiver. Ainda parece que UITableViewvence a batalha pelo dimensionamento dinâmico do IMHO.

Ressalvas

Lembre-se de que, se você estiver usando células protótipo para calcular o estimadoItemSize - isso será interrompido se o seu XIB usar classes de tamanho . A razão para isso é que, quando você carrega sua célula de um XIB, sua classe de tamanho será configurada Undefined. Isso só será interrompido no iOS 8 ou superior, já que no iOS 7 a classe de tamanho será carregada com base no dispositivo (iPad = Regular-Qualquer, iPhone = Compact-Qualquer). Você pode definir o estimadoItemSize sem carregar o XIB ou carregar a célula do XIB, adicioná-la ao collectionView (isso definirá o traitCollection), executar o layout e removê-lo da superview. Como alternativa, você também pode fazer com que seu celular substitua o traitCollectiongetter e retorne as características apropriadas. Você decide.

Deixe-me saber se eu perdi alguma coisa, espero ter ajudado e boa sorte na codificação


Daniel Galasko
fonte
2
Estou tentando implementar a mesma coisa com o layout de fluxo, no entanto, quando eu retorno o newFrame com tamanho atualizado, o layout não parece recalcular as posições y, que ainda são baseadas na altura em estimadoSize. O que está a faltar?
Anna #
@anna você está chamando invalidateLayout no layout?
Daniel Galasko
1
Ah, encontrei a diferença, você definiu a altura estimada bastante alta (400) enquanto eu estava fazendo o mínimo (44). Isso ajudou. Mas o contentSize ainda parece não estar sendo definido corretamente até você percorrer todo o caminho, portanto ainda não é muito utilizável. Btw, eu mudei UITextView para UILabel, mesmo comportamento.
anna
17
Isso parece estar fundamentalmente quebrado no iOS 9 GM. A configuração estimatedItemSizeleva a falhas - parece haver algum bug enorme na maneira como o layout automático está tentando processar o UICollectionViewCell.
9788 Wesleye
3
Correndo para um problema semelhante, alguém encontrou uma solução?
Tony
47

No iOS10, há uma nova constante chamada UICollectionViewFlowLayout.automaticSize(anteriormente UICollectionViewFlowLayoutAutomaticSize); portanto, em vez disso:

self.flowLayout.estimatedItemSize = CGSize(width: 100, height: 100)

você pode usar isso:

self.flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

Ele tem melhor desempenho, especialmente quando as células na exibição de coleção têm wid constante

Acessando o layout de fluxo:

override func viewDidLoad() {
   super.viewDidLoad()

   if let flowLayout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
      flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
   }
}

Swift 5 Atualizado:

override func viewDidLoad() {
   super.viewDidLoad()

   if let flowLayout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
      flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
    }
}
pawel221
fonte
8
Onde você define isso? em viewdidload? Como você lida com o layout do fluxo?
UKDataGeek
1
Eu consegui implementar isso - mas ele tem um comportamento estranho que faz com que ele centralize as células se houver uma única célula. Aqui está a pergunta de estouro de pilha sobre isso - me perplexo: stackoverflow.com/questions/43266924/...
UKDataGeek
1
Você pode acessar o layout do fluxo por meio de collectionViews collectionViewLayoute apenas verificar se é do tipoUICollectionViewFlowLayout
d4Rk
4
torna minhas células muito pequenas e inúteis
user924
44

Algumas mudanças importantes na resposta de Daniel Galasko todos os meus problemas. Infelizmente, ainda não tenho reputação suficiente para comentar diretamente.

Na etapa 1, ao usar o Layout automático, basta adicionar um UIView pai único à célula. TUDO dentro da célula deve ser uma subvisão do pai. Isso respondeu a todos os meus problemas. Enquanto o Xcode adiciona isso para o UITableViewCells automaticamente, não o faz (mas deveria) para o UICollectionViewCells. De acordo com os documentos :

Para configurar a aparência da sua célula, adicione as visualizações necessárias para apresentar o conteúdo do item de dados como subvisões à visualização na propriedade contentView. Não adicione diretamente subvisões à própria célula.

Em seguida, pule completamente a etapa 3. Não é necessário.

Matt Koala
fonte
1
Interessante; isso é especificamente para o Xibs, mas, mesmo assim, obrigado, tentarei me confundir com o projeto Github e ver se consigo reproduzir. Talvez dividir a resposta em Xib vs programático
Daniel Galasko
Muito útil. Obrigado!
ProblemSlover
2
iOS 9, Xcode 7. As células são protegidas no storyboard, defina a subclasse personalizada. Tentando criar uma propriedade chamada contentView, o Xcode reclama que está em conflito com a propriedade existente. Tentei adicionar subvisões self.contentViewe definir restrições para isso e o aplicativo trava.
Nicolas Miari 6/10/2015
@NicolasMiari Eu não sei como fazer isso programaticamente, minha solução é realmente apenas adicionar uma única visualização no XIB, onde tudo está dentro dele. Você não precisa criar uma propriedade nem nada.
Matt Koala
Estou enfrentando o mesmo problema que @NicolasMiari. Quando eu definir o estimatedContentSize para células prototipados no storyboard com restrições autolayout no iOS 9 / Xcode 7 o aplicativo trava com uma exceção de acesso ruim e não stacktrace útil
wasabi
34

No iOS 10+, esse é um processo muito simples de duas etapas.

  1. Certifique-se de que todo o conteúdo da sua célula seja colocado em um único UIView (ou dentro de um descendente do UIView, como o UIStackView, que simplifica muito o layout automático). Assim como no redimensionamento dinâmico de UITableViewCells, toda a hierarquia de visualizações precisa ter restrições configuradas, do contêiner mais externo à mais interna. Isso inclui restrições entre o UICollectionViewCell e o childview imediato

  2. Instrua o layout do fluxo do seu UICollectionView a dimensionar automaticamente

    yourFlowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
Marmoy
fonte
Estou curioso, como o empacotamento do conteúdo de suas células em um UIView tornaria o Auto Layout melhor? Eu pensei que teria um desempenho melhor com uma hierarquia mais superficial?
James Heald
1
Não mencionei performance, apenas simplicidade. As células de dimensionamento automático funcionarão apenas se houver restrições que abranjam todo o caminho entre esquerda e direita e entre a parte superior e a inferior. Conseguir isso é mais fácil quando todas as visualizações são agrupadas em um único contêiner. Se esse contêiner for um UIStackView, é mais fácil que se for qualquer outro descendente do UIView.
Marmoy
Você pode me dizer como definir a altura constante (como definir a altura 25 e a largura será alterada automaticamente) para UICollectionViewFlowLayoutAutomaticSize.
Svetoslav Atanasov
Usando o Swift 4.1, o Xcode me diz que UICollectionViewFlowLayout.automaticSizefoi renomeado para UICollectionViewFlowLayoutAutomaticSize.
LinusGeffarth 22/02/19
14
  1. Adicione flowLayout em viewDidLoad ()

    override func viewDidLoad() {
        super.viewDidLoad() 
        if let flowLayout = infoCollection.collectionViewLayout as? UICollectionViewFlowLayout {
            flowLayout.estimatedItemSize = CGSize(width: 1, height:1)
        }
    }
  2. Além disso, defina um UIView como mainContainer para sua célula e adicione todas as visualizações necessárias dentro dela.

  3. Consulte este incrível e impressionante tutorial para obter mais referências: UICollectionView com célula de dimensionamento automático usando o autolayout no iOS 9 e 10

dianakarenms
fonte
11

EDIT 19/19/19: para o iOS 13, basta usar UICollectionViewCompositionalLayout com alturas estimadas. Não perca seu tempo lidando com essa API quebrada.

Depois de lutar com isso por algum tempo, notei que o redimensionamento não funciona para o UITextViews se você não desativar a rolagem:

let textView = UITextView()
textView.scrollEnabled = false
noobular
fonte
quando tento rolar, a célula de exibição de coleção não é suave. Você tem alguma solução para isso?
Sathish Kumar Gurunathan
6

mistério da âncora contentView:

Em um caso bizarro, isso

    contentView.translatesAutoresizingMaskIntoConstraints = false

não funcionaria. Adicionadas quatro âncoras explícitas ao contentView e ele funcionou.

class AnnoyingCell: UICollectionViewCell {
    
    @IBOutlet var word: UILabel!
    
    override init(frame: CGRect) {
        super.init(frame: frame); common() }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder); common() }
    
    private func common() {
        contentView.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            contentView.leftAnchor.constraint(equalTo: leftAnchor),
            contentView.rightAnchor.constraint(equalTo: rightAnchor),
            contentView.topAnchor.constraint(equalTo: topAnchor),
            contentView.bottomAnchor.constraint(equalTo: bottomAnchor)
        ])
    }
}

e como sempre

    estimatedItemSize = UICollectionViewFlowLayout.automaticSize

no YourLayout: UICollectionViewFlowLayout

Quem sabe? Pode ajudar alguém.

Crédito

https://www.vadimbulavin.com/collection-view-cells-self-sizing/

tropeçou na ponta lá - nunca a vi em nenhum outro lugar em todos os artigos da 1000s sobre isso.

Fattie
fonte
3

Eu fiz uma altura de célula dinâmica da exibição de coleção. Aqui está o repositório git hub .

E descubra por que o preferidoLayoutAttributesFittingAttributes é chamado mais de uma vez. Na verdade, ele será chamado pelo menos 3 vezes.

A imagem do log do console: insira a descrição da imagem aqui

1º preferidoLayoutAttributesFittingAttributes :

(lldb) po layoutAttributes
<UICollectionViewLayoutAttributes: 0x7fa405c290e0> index path: (<NSIndexPath:    0xc000000000000016> 
{length = 2, path = 0 - 0}); frame = (15 12; 384 57.5); 

(lldb) po self.collectionView
<UICollectionView: 0x7fa40606c800; frame = (0 57.6667; 384 0);

O layoutAttributes.frame.size.height é o status atual 57.5 .

2º preferidoLayoutAttributesFittingAttributes :

(lldb) po layoutAttributes
<UICollectionViewLayoutAttributes: 0x7fa405c16370> index path: (<NSIndexPath: 0xc000000000000016> 
{length = 2, path = 0 - 0}); frame = (15 12; 384 534.5); 

(lldb) po self.collectionView
<UICollectionView: 0x7fa40606c800; frame = (0 57.6667; 384 0);

A altura do quadro da célula foi alterada para 534,5 conforme o esperado. Mas, a exibição de coleção ainda tem altura zero.

Terceiro preferidoLayoutAttributesFittingAttributes :

(lldb) po layoutAttributes
<UICollectionViewLayoutAttributes: 0x7fa403d516a0> index path: (<NSIndexPath: 0xc000000000000016> 
{length = 2, path = 0 - 0}); frame = (15 12; 384 534.5); 

(lldb) po self.collectionView
<UICollectionView: 0x7fa40606c800; frame = (0 57.6667; 384 477);

Você pode ver que a altura da visualização da coleção foi alterada de 0 para 477 .

O comportamento é semelhante ao manipular rolagem:

1. Before self-sizing cell

2. Validated self-sizing cell again after other cells recalculated.

3. Did changed self-sizing cell

No começo, pensei que esse método chamasse apenas uma vez. Então, eu codifiquei da seguinte maneira:

CGRect frame = layoutAttributes.frame;
frame.size.height = frame.size.height + self.collectionView.contentSize.height;
UICollectionViewLayoutAttributes* newAttributes = [layoutAttributes copy];
newAttributes.frame = frame;
return newAttributes;

Está linha:

frame.size.height = frame.size.height + self.collectionView.contentSize.height;

causará loop infinito na chamada do sistema e falha no aplicativo.

Qualquer tamanho alterado, ele validará oLayoutAttributesFittingAttributes preferido de todas as células repetidamente até que as posições de todas as células (ou seja, quadros) não sejam mais alteradas.

Yang Young
fonte
2

Além das respostas acima,

Apenas certifique-se de definir propriedade estimadoItemSize de UICollectionViewFlowLayout para algum tamanho e não implemente sizeForItem: atIndexPath método delegado.

É isso aí.

Murat Yasar
fonte
1

Se você implementar o método UICollectionViewDelegateFlowLayout:

- (CGSize)collectionView:(UICollectionView*)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath*)indexPath

Quando você liga collectionview performBatchUpdates:completion:, a altura do tamanho será usada em sizeForItemAtIndexPathvez de preferredLayoutAttributesFittingAttributes .

O processo de renderização performBatchUpdates:completionpassará pelo método, preferredLayoutAttributesFittingAttributesmas ignorará suas alterações.

Yang Young
fonte
Eu já vi esse mau comportamento, mas apenas quando o tamanho estimado é zero. Tem certeza de que está definindo um tamanho estimado?
precisa saber é o seguinte
1

Para quem puder ajudar,

Eu tive aquele acidente desagradável se estimatedItemSizefoi definido. Mesmo se eu retornasse 0 pol numberOfItemsInSection. Portanto, as próprias células e seu layout automático não foram a causa da falha ... O collectionView apenas travou, mesmo quando vazio, apenas porqueestimatedItemSize foi configurado para o dimensionamento automático.

No meu caso, reorganizei meu projeto, de um controlador contendo um collectionView para um collectionViewController, e funcionou.

Vai saber.

Jean Le Moignan
fonte
1
Tente ligar collectionView.collectionViewLayout.invalidateLayout()depois collectionView.reloadData().
chengsam
para ios10 e abaixo, adicione este código `substitua a função viewWillLayoutSubviews () {super.viewWillLayoutSubviews () if #available (iOS 11.0, *) {} else {mainCollectionView.collectionViewLayout.invalidateLayout ()}}`
Marosdee Uma
1

Para quem tentou de tudo sem sorte, essa é a única coisa que faz com que funcione para mim. Para os rótulos de várias linhas dentro da célula, tente adicionar esta linha mágica:

label.preferredMaxLayoutWidth = 200

Mais informações: aqui

Felicidades!

HelloimDarius
fonte
0

O método de exemplo acima não compila. Aqui está uma versão corrigida (mas não testada para saber se funciona ou não).

override func preferredLayoutAttributesFittingAttributes(layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes 
{
    let attr: UICollectionViewLayoutAttributes = layoutAttributes.copy() as! UICollectionViewLayoutAttributes

    var newFrame = attr.frame
    self.frame = newFrame

    self.setNeedsLayout()
    self.layoutIfNeeded()

    let desiredHeight: CGFloat = self.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
    newFrame.size.height = desiredHeight
    attr.frame = newFrame
    return attr
}
user3246173
fonte
0

Atualize mais informações:

  • Se você usar flowLayout.estimatedItemSize, sugira usar a versão posterior do iOS8.3. Antes do iOS8.3, ele falha [super layoutAttributesForElementsInRect:rect];. A mensagem de erro é

    *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil'

  • Segundo, na versão iOS8.x, flowLayout.estimatedItemSizefará com que configurações diferentes de inserção de seção não funcionem. ou seja, a função: (UIEdgeInsets)collectionView:layout:insetForSectionAtIndex:.

Yang Young
fonte
0

A solução compreende 4 etapas importantes:

  1. Ativando o dimensionamento dinâmico de células

flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

  1. Ative explicitamente o layout automático e fixe contentView-o nas bordas da célula, pois contentViewimpede o dimensionamento automático, pois ele não tem o layout automático ativado.
class MultiLineCell: UICollectionViewCell{

    private var textViewHeightContraint: NSLayoutConstraint!

    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .cyan
        contentView.translatesAutoresizingMaskIntoConstraints = false
        contentViewWidthAnchor = contentView.widthAnchor.constraint(equalToConstant: 0)

        NSLayoutConstraint.activate([
            contentView.leadingAnchor.constraint(equalTo: leadingAnchor),
            contentView.topAnchor.constraint(equalTo: topAnchor),
            contentView.trailingAnchor.constraint(equalTo: trailingAnchor),
            contentView.bottomAnchor.constraint(equalTo: bottomAnchor),
        ])
    } 
    ...
}
  1. Defina contentView.widthAnchor.constraint de collectionView(:cellForItemAt:)para limitar a largura do contentView à largura do collectionView.

Isso acontece da seguinte maneira; vamos definir célula maxWidthvariável para largura de CollectionView em collectionView(:cellForItemAt:)e em maxWidth's didSetmétodo que vai definir widthAnchor.constant para maxWidth.

class ViewController: UIViewController, UICollectionViewDataSource {
    ...

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath) as! MultiLineCell
        cell.textView.text = dummyTextMessages[indexPath.row]
        cell.maxWidth = collectionView.frame.width
        return cell
    }

    ...
}
class MultiLineCell: UICollectionViewCell{
    ....

    var maxWidth: CGFloat? {
        didSet {
            guard let maxWidth = maxWidth else {
                return
            }
            contentViewWidthAnchor.constant = maxWidth
            contentViewWidthAnchor.isActive = true
        }
    }

    ....
}

Como você deseja ativar o dimensionamento automático do UITextView, ele possui uma etapa adicional para;

4. Calcule e defina o heightAnchor.constant do UITextView.

Assim, sempre que a largura da contentView está definido vamos ajustar a altura de UITextView ao longo de didSetde maxWidth.

Por dentro do UICollectionViewCell:

var maxWidth: CGFloat? {
    didSet {
        guard let maxWidth = maxWidth else {
            return
        }
        contentViewWidthAnchor.constant = maxWidth
        contentViewWidthAnchor.isActive = true

        let sizeToFitIn = CGSize(width: maxWidth, height: CGFloat(MAXFLOAT))
        let newSize = self.textView.sizeThatFits(sizeToFitIn)
        self.textViewHeightContraint.constant = newSize.height
    }
}

Essas etapas obterão o resultado desejado. Aqui está uma essência executável completa

Agradecemos a Vadim Bulavin por sua postagem clara e esclarecedora no blog - Tamanho da exibição das células: tutorial passo a passo

Harley
fonte
-2

Tentei usar, estimatedItemSizemas havia muitos bugs ao inserir e excluir células, se a estimatedItemSizealtura não fosse exatamente igual à da célula. parei de definir estimatedItemSizee implementei células dinâmicas usando uma célula protótipo. aqui está como isso é feito:

crie este protocolo:

protocol SizeableCollectionViewCell {
    func fittedSize(forConstrainedSize size: CGSize)->CGSize
}

implemente este protocolo em seu costume UICollectionViewCell:

class YourCustomCollectionViewCell: UICollectionViewCell, SizeableCollectionViewCell {

    @IBOutlet private var mTitle: UILabel!
    @IBOutlet private var mDescription: UILabel!
    @IBOutlet private var mContentView: UIView!
    @IBOutlet private var mTitleTopConstraint: NSLayoutConstraint!
    @IBOutlet private var mDesciptionBottomConstraint: NSLayoutConstraint!

    func fittedSize(forConstrainedSize size: CGSize)->CGSize {

        let fittedSize: CGSize!

        //if height is greatest value, then it's dynamic, so it must be calculated
        if size.height == CGFLoat.greatestFiniteMagnitude {

            var height: CGFloat = 0

            /*now here's where you want to add all the heights up of your views.
              apple provides a method called sizeThatFits(size:), but it's not 
              implemented by default; except for some concrete subclasses such 
              as UILabel, UIButton, etc. search to see if the classes you use implement 
              it. here's how it would be used:
            */
            height += mTitle.sizeThatFits(size).height
            height += mDescription.sizeThatFits(size).height
            height += mCustomView.sizeThatFits(size).height    //you'll have to implement this in your custom view

            //anything that takes up height in the cell has to be included, including top/bottom margin constraints
            height += mTitleTopConstraint.constant
            height += mDescriptionBottomConstraint.constant

            fittedSize = CGSize(width: size.width, height: height)
        }
        //else width is greatest value, if not, you did something wrong
        else {
            //do the same thing that's done for height but with width, remember to include leading/trailing margins in calculations
        }

        return fittedSize
    }
}

Agora faça com que o seu controlador esteja em conformidade com UICollectionViewDelegateFlowLayout, e nele, tenha este campo:

class YourViewController: UIViewController, UICollectionViewDelegateFlowLayout {
    private var mCustomCellPrototype = UINib(nibName: <name of the nib file for your custom collectionviewcell>, bundle: nil).instantiate(withOwner: nil, options: nil).first as! SizeableCollectionViewCell
}

será usado como uma célula de protótipo para vincular dados e, em seguida, determinar como esses dados afetaram a dimensão que você deseja que seja dinâmica

finalmente, o UICollectionViewDelegateFlowLayout's collectionView(:layout:sizeForItemAt:)deve ser implementado:

class YourViewController: UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {

    private var mDataSource: [CustomModel]

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath)->CGSize {

        //bind the prototype cell with the data that corresponds to this index path
        mCustomCellPrototype.bind(model: mDataSource[indexPath.row])    //this is the same method you would use to reconfigure the cells that you dequeue in collectionView(:cellForItemAt:). i'm calling it bind

        //define the dimension you want constrained
        let width = UIScreen.main.bounds.size.width - 20    //the width you want your cells to be
        let height = CGFloat.greatestFiniteMagnitude    //height has the greatest finite magnitude, so in this code, that means it will be dynamic
        let constrainedSize = CGSize(width: width, height: height)

        //determine the size the cell will be given this data and return it
        return mCustomCellPrototype.fittedSize(forConstrainedSize: constrainedSize)
    }
}

e é isso. Retornar o tamanho da célula collectionView(:layout:sizeForItemAt:)dessa maneira, impedindo-me de usar estimatedItemSizee inserir e excluir células funciona perfeitamente.

sapato
fonte