Largura de célula dinâmica de UICollectionView dependendo da largura do rótulo

92

Eu tenho um UICollectionView, que carrega células de células reutilizáveis, que contém rótulo. Uma matriz fornece conteúdo para esse rótulo. Posso redimensionar a largura da etiqueta dependendo da largura do conteúdo facilmente com sizeToFit. Mas não posso fazer célula para caber na etiqueta.

Aqui está o código

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    arrayOfStats =  @[@"time:",@"2",@"items:",@"10",@"difficulty:",@"hard",@"category:",@"main"];
}

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:     (NSInteger)section{
    return [arrayOfStats count];
}

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

    return CGSizeMake(??????????);
}

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{

    return 1;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{

    Cell *cell = (Cell *) [collectionView dequeueReusableCellWithReuseIdentifier:@"qw" forIndexPath:indexPath];
    cell.myLabel.text = [NSString stringWithFormat:@"%@",[arrayOfStats objectAtIndex:indexPath.item]];
    // make label width depend on text width
    [cell.myLabel sizeToFit];

    //get the width and height of the label (CGSize contains two parameters: width and height)
    CGSize labelSize = cell.myLbale.frame.size;

    NSLog(@"\n width  = %f height = %f", labelSize.width,labelSize.height);

    return cell;
}
polpa
fonte
tipo de problema semelhante ... stackoverflow.com/questions/24915443/… ???
Fattie

Respostas:

83

Em sizeForItemAtIndexPathtroca o tamanho do texto

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

    return [(NSString*)[arrayOfStats objectAtIndex:indexPath.row] sizeWithAttributes:NULL];
}
Basheer_CAD
fonte
4
Você não pode imaginar como eu te agradeço! Isso realmente funciona. Agora só preciso resolver o problema de [cell.myLabel sizeToFit], porque ele aparece em seu tamanho real somente após a rolagem. Mas não cheguei nem perto de sua solução.
celulose de
obrigado por sua ajuda, mas ainda tenho um problema. Quando eu descomentei [cell.myLabel sizeToFit], eu tenho as palavras truncadas e as letras cortadas na parte inferior, mas fica ok depois que eu rolar (as palavras têm tamanho normal e as letras saltam um pouco). Se eu comentar e desabilitar a mensagem [cell.myLabel sizeToFit] (decidi brincar com o IB e funciona bem), tenho palavras cortadas no final e na parte inferior. Eu fiz uma captura de tela goo.gl/HaoqQV Não é muito nítida em telas não retina, mas você pode ver que as letras foram cortadas. Sua sugestão de como resolver será muito apreciada!
celulose de
2
em vez de sizeToFit, use sizeWithAttributes para obter o CGSize do texto e, em seguida, defina o quadro do rótulo com o novo tamanho.
Basheer_CAD
obrigado pela sugestão, mas ainda tenho myLabel cortado na parte inferior e no final. Talvez eu esteja errado com a implementação de sua sugestão. Aqui está meu código
polpa de
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{ Cell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"qw" forIndexPath:indexPath]; cell.myLbale.text = [NSString stringWithFormat:@"%@",[arrayOfStats objectAtIndex:indexPath.item]]; CGSize textSize; textSize = [[arrayOfStats objectAtIndex:indexPath.item] sizeWithAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:12.0f]}]; [cell.myLbale sizeThatFits:textSize]; //[cell.myLbale sizeToFit]; return cell; }
celulose de
48

Swift 4.2+

O princípio é:

  1. Certifique-se de que a delegação esteja configurada (por exemplo collectionView.delegate = self)

  2. Implementar UICollectionViewDelegateFlowLayout(contém a assinatura do método necessária).

  3. collectionView...sizeForItemAtMétodo de chamada .

  4. Não há necessidade de ponte-cast Stringpara NSStringa chamada size(withAttributes:de método. Swift Stringjá vem pronto para uso.

  5. Os atributos são os mesmos que você definiu (NS)AttributedString, ou seja, família da fonte, tamanho, peso, etc. Parâmetro opcional.


Solução de amostra:

extension ViewController: UICollectionViewDelegateFlowLayout {
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return "String".size(withAttributes: nil)
    }
}

Mas você provavelmente desejaria especificar atributos de string concretos respectivos para sua célula, portanto, o retorno final seria semelhante a:

extension ViewController: UICollectionViewDelegateFlowLayout {
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        // dataArary is the managing array for your UICollectionView.
        let item = dataArray[indexPath.row]
        let itemSize = item.size(withAttributes: [
            NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: 14)
        ])
        return itemSize
    }
}

Por que você NÃO DEVE usar UILabel para calcular o tamanho? Esta é a solução sugerida:

let label = UILabel(frame: CGRect.zero)
label.text = textArray[indexPath.item]
label.sizeToFit()

Sim, você obtém o mesmo resultado. Parece simplista e pode parecer uma solução ideal. Mas é impróprio porque: 1) é caro, 2) sobrecarregado e 3) sujo.

É caro porque UILabel é um objeto de IU complexo, que está sendo criado a cada iteração sempre que sua célula está prestes a ser exibida, mesmo que você não precise dela aqui. É uma solução de overhead porque você só precisa obter o tamanho de um texto, mas vai tão longe a ponto de criar um objeto de IU inteiro. E é sujo por esse motivo.

Hexfire
fonte
1
não se esqueça de definircollectionView.delegate == self // or whatever-object-which-do-it
Fitsyu
Ótima resposta. Embora o tamanho que eu estava comprando fosse um pouco menor do que o necessário, em diferentes comprimentos de cordas, decidi modificar um pouco o tamanho. Adicionar 5 pontos adicionais à largura funcionou:CGSize(width: title.size(withAttributes: [NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: 16)]).width + 5, height: 50)
Starsky
37

Eu encontrei um pequeno truque para o Swift 4.2

Para largura dinâmica e altura fixa:

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        let label = UILabel(frame: CGRect.zero)
        label.text = textArray[indexPath.item]
        label.sizeToFit()
        return CGSize(width: label.frame.width, height: 32)
    }

Para altura dinâmica e largura fixa:

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
            let label = UILabel(frame: CGRect.zero)
            label.text = textArray[indexPath.item]
            label.sizeToFit()
            return CGSize(width: 120, height: label.frame.height)
        }
Hassan Izhar
fonte
8
Tenha cuidado ao usar isso. Criar e desenhar um novo UILabel para cada cálculo de célula é muito caro.
AnthonyR
precisa adicionar UICollectionViewDelegateFlowLayout
cristianego
3
Para abordar o comentário sobre ser caro criar um rótulo fictício, talvez você pudesse apenas criar um rótulo fictício em vez de vários. Tudo o que você realmente deseja é o tamanho do texto dos atributos do rótulo. No final do dia, porém, é essencialmente o mesmo que calculado o tamanho do texto via sizeWithAttributes, então talvez essa seja a resposta preferida.
Stephen Paul
@Hassan Obrigado mano funcionou para mim, mas eu encontrei um problema na largura então adicionei o retorno CGSize (largura: label.frame.width + 50, height: 32). Então funcionou, acho que essa resposta deveria estar no topo da lista.
Arshad Shaik
27

Verifique abaixo o código que você pode estar fornecendo CGSize muito curto.

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

    NSString *testString = @"SOME TEXT";
    return [testString sizeWithAttributes:NULL];
}
Raza.najam
fonte
@Vineesh TP Obrigado por sua resposta, com certeza irei dar uma olhada e deixar você saber!
celulose
+1000 Este definitivamente funciona. Sua postagem e a de Basheer devem ter uma marca de seleção verde juntas.
guaxinim rochoso de
Alguma ideia de como fazer isso com o swift 3?
UKDataGeek
1
Obrigado cara, é hora de festa !!
Ravi de
18

Em Swift 3

let size = (arrayOfStats[indexPath.row] as NSString).size(attributes: nil)
Johnson
fonte
14

Swift 4

let size = (arrayOfStats[indexPath.row] as NSString).size(withAttributes: nil)
Barath
fonte
0

// adicionar à vista didload

UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
    [layout setScrollDirection:UICollectionViewScrollDirectionHorizontal];
    layout.estimatedItemSize = CGSizeMake(self.breadScrumbCollectionView.frame.size.width, 30); 
self.breadScrumbCollectionView.collectionViewLayout = layout;
Apurva Gaikwad
fonte