Você pode animar uma alteração de altura em um UITableViewCell quando selecionado?


393

Estou usando um UITableViewno meu aplicativo para iPhone e tenho uma lista de pessoas que pertencem a um grupo. Gostaria que, quando o usuário clicar em uma pessoa em particular (selecionando a célula), a célula cresça em altura para exibir vários controles da interface do usuário para editar as propriedades dessa pessoa.

Isso é possível?

Respostas:


879

Encontrei uma solução MUITO SIMPLES para isso como um efeito colateral de um UITableViewtrabalho em que eu estava .....

Armazene a altura da célula em uma variável que reporte a altura original normalmente via tableView: heightForRowAtIndexPath:, e quando desejar animar uma alteração de altura, basta alterar o valor da variável e chamar isso ...

[tableView beginUpdates];
[tableView endUpdates];

Você descobrirá que não faz uma recarga completa, mas é suficiente para UITableViewsaber que precisa redesenhar as células, capturando o novo valor de altura da célula ... e adivinhe? ANIMA a mudança para você. Doce.

Eu tenho uma explicação mais detalhada e exemplos de código completo no meu blog ... Animar alteração de altura da célula UITableView


6
Isto é brilhante. Mas existe alguma maneira de controlar a velocidade da animação da tabela?
22812 Josh Kahane

7
Isso funciona, mas se eu aumentar uma célula, digamos 44 a 74 e depois diminuí-la novamente para 44, a linha separadora será totalmente estranha. Alguém pode confirmar isso?
plaetzchen

46
Essa é uma solução bizarra, mas é o que a Apple recomenda na sessão "Mastering Table View" da WWDC 2010. Vou registrar um bug ao adicionar isso à documentação porque passei cerca de duas horas pesquisando.
bpapa

5
Eu tentei esta solução e ela só funciona às vezes, quando você pressiona uma célula, há 50% de probabilidade de funcionar. Alguém tem o mesmo bug? É porque iOS7?
perfil completo de Joan Cardona

7
Agora também está escrito na documentação oficial: You can also use this method followed by the endUpdates method to animate the change in the row heights without reloading the cell. developer.apple.com/library/ios/documentation/UIKit/Reference/…
Jaroslav

63

Eu gosto da resposta de Simon Lee. Na verdade, eu não tentei esse método, mas parece que ele mudaria o tamanho de todas as células da lista. Eu esperava uma alteração apenas da célula que é tocada. Eu meio que fiz isso como Simon, mas com apenas uma pequena diferença. Isso mudará a aparência de uma célula quando ela for selecionada. E isso anima. Apenas outra maneira de fazer isso.

Crie um int para manter um valor para o índice de célula selecionado atual:

int currentSelection;

Então:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    int row = [indexPath row];
    selectedNumber = row;
    [tableView beginUpdates];
    [tableView endUpdates];
}

Então:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {

    if ([indexPath row] == currentSelection) {
        return  80;
    }
    else return 40;


}

Tenho certeza de que você pode fazer alterações semelhantes no tableView: cellForRowAtIndexPath: para alterar o tipo de célula ou até carregar um arquivo xib para a célula.

Assim, o currentSelection começará em 0. Você precisaria fazer ajustes se não quisesse que a primeira célula da lista (no índice 0) parecesse selecionada por padrão.


2
Verifique o código anexado ao meu post, fiz exatamente isso, duplique a altura de uma célula quando selecionado. :)
Simon Lee

8
"Na verdade, eu não tentei esse método, mas parece que ele mudaria o tamanho de todas as células da lista" - então você não parecia muito exigente.
11289 jamie

13
A seleção atual já está armazenada em tableView.indexPathForSelectedRow.
Nick

22

Adicione uma propriedade para acompanhar a célula selecionada

@property (nonatomic) int currentSelection;

Defina-o como um valor sentinela em (por exemplo) viewDidLoad, para garantir que o UITableViewinício na posição 'normal'

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view.

    //sentinel
    self.currentSelection = -1;
}

Em heightForRowAtIndexPathvocê pode definir a altura desejada para a célula selecionada

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    int rowHeight;
    if ([indexPath row] == self.currentSelection) {
        rowHeight = self.newCellHeight;
    } else rowHeight = 57.0f;
    return rowHeight;
}

Ao didSelectRowAtIndexPathsalvar a seleção atual e salvar uma altura dinâmica, se necessário

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
        // do things with your cell here

        // set selection
        self.currentSelection = indexPath.row;
        // save height for full text label
        self.newCellHeight = cell.titleLbl.frame.size.height + cell.descriptionLbl.frame.size.height + 10;

        // animate
        [tableView beginUpdates];
        [tableView endUpdates];
    }
}

Em didDeselectRowAtIndexPathdefinir o índice de seleção de volta ao valor sentinela e animar a célula de volta à forma normal

- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath {       
        // do things with your cell here

        // sentinel
        self.currentSelection = -1;

        // animate
        [tableView beginUpdates];
        [tableView endUpdates];
    }
}

Obrigado, obrigado, obrigado! Eu adicionei um pouco de código para tornar a célula capaz de alternar. Eu adicionei o código abaixo.
Septronic

14

reloadData não é bom porque não há animação ...

Isto é o que estou tentando atualmente:

NSArray* paths = [NSArray arrayWithObject:[NSIndexPath indexPathForRow:0 inSection:0]];
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationFade];
[self.tableView deleteRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];

Quase funciona direito. Quase. Estou aumentando a altura da célula e, às vezes, há um pequeno "soluço" na visualização da tabela à medida que a célula é substituída, como se alguma posição de rolagem na visualização da tabela estivesse sendo preservada, a nova célula (que é a primeira célula na tabela) termina com o deslocamento muito alto e a visualização de rolagem é exibida para reposicioná-lo.


Pessoalmente, descobri que o uso desse método, mas com UITableViewRowAnimationNone, fornece um resultado mais suave, mas ainda não perfeito.
Ron Srebro

11

Não sei o que todo esse material sobre como chamar beginUpdates / endUpdates em sucessão é: você pode apenas usar -[UITableView reloadRowsAtIndexPaths:withAnimation:]. Aqui está um exemplo de projeto .


Excelente, isso não estica minhas visualizações de texto na minha célula de layout automático. Mas ele precisa ter um cintilação na animação, porque a opção Nenhuma animação parece defeituosa ao atualizar o tamanho da célula.
H3dkandi

10

Eu resolvi com reloadRowsAtIndexPaths.

Eu salvo no didSelectRowAtIndexPathindexPath da célula selecionada e ligo reloadRowsAtIndexPathsno final (você pode enviar o NSMutableArray para a lista de elementos que deseja recarregar).

Em heightForRowAtIndexPathvocê pode verificar se indexPath está na lista ou não da cela de expandIndexPath e altura de envio.

Você pode verificar este exemplo básico: https://github.com/ferminhg/iOS-Examples/tree/master/iOS-UITableView-Cell-Height-Change/celdascambiadetam É uma solução simples.

eu adiciono um tipo de código se te ajudar

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return 20;
}

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath: (NSIndexPath*)indexPath
{
    if ([indexPath isEqual:_expandIndexPath])
        return 80;

    return 40;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *CellIdentifier = @"Celda";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    [cell.textLabel setText:@"wopwop"];

    return cell;
}

#pragma mark -
#pragma mark Tableview Delegate Methods

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    NSMutableArray *modifiedRows = [NSMutableArray array];
    // Deselect cell
    [tableView deselectRowAtIndexPath:indexPath animated:TRUE];
    _expandIndexPath = indexPath;
    [modifiedRows addObject:indexPath];

    // This will animate updating the row sizes
    [tableView reloadRowsAtIndexPaths:modifiedRows withRowAnimation:UITableViewRowAnimationAutomatic];
}


3

Tente isto para expandir a linha indexwise:

@property (nonatomic) NSIndexPath *expandIndexPath;
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath
{
if ([indexPath isEqual:self.expandedIndexPath])
    return 100;

return 44;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSMutableArray *modifiedRows = [NSMutableArray array];
if ([indexPath isEqual:self.expandIndexPath]) {
    [modifiedRows addObject:self.expandIndexPath];
    self.expandIndexPath = nil;
} else {
    if (self.expandedIndexPath)
        [modifiedRows addObject:self.expandIndexPath];

    self.expandIndexPath = indexPath;
    [modifiedRows addObject:indexPath];
}

// This will animate updating the row sizes
[tableView reloadRowsAtIndexPaths:modifiedRows withRowAnimation:UITableViewRowAnimationAutomatic];

// Preserve the deselection animation (if desired)
[tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ViewControllerCellReuseIdentifier];
    cell.textLabel.text = [NSString stringWithFormat:@"I'm cell %ld:%ld", (long)indexPath.section, (long)indexPath.row];

return cell;
}

3
BOOL flag;

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    flag = !flag;
    [tableView beginUpdates];
    [tableView reloadRowsAtIndexPaths:@[indexPath] 
                     withRowAnimation:UITableViewRowAnimationAutomatic];
    [tableView endUpdates];
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return YES == flag ? 20 : 40;
}

2

apenas uma observação para alguém como eu pesquisando adicionar "Mais detalhes" na célula personalizada.

[tableView beginUpdates];
[tableView endUpdates];

Fez um excelente trabalho, mas não se esqueça de "cortar" a exibição da célula. No Interface Builder, selecione sua célula -> Exibição de conteúdo -> no Property Inspector e selecione " Subvisão de clipe "


2

Heres uma versão mais curta da resposta de Simons para Swift 3. Também permite alternar a seleção da célula

var cellIsSelected: IndexPath?


  func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    cellIsSelected = cellIsSelected == indexPath ? nil : indexPath
    tableView.beginUpdates()
    tableView.endUpdates()
  }


  func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    if cellIsSelected == indexPath {
      return 250
    }
    return 65
  }

2

Swift 4 e acima

adicione o código abaixo ao método de delegação de linha didselect do tableview

tableView.beginUpdates()
tableView.setNeedsLayout()
tableView.endUpdates()

1

Versão rápida da resposta de Simon Lee.

// MARK: - Variables 
  var isCcBccSelected = false // To toggle Bcc.



    // MARK: UITableViewDelegate
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {

    // Hide the Bcc Text Field , until CC gets focused in didSelectRowAtIndexPath()
    if self.cellTypes[indexPath.row] == CellType.Bcc {
        if (isCcBccSelected) {
            return 44
        } else {
            return 0
        }
    }

    return 44.0
}

Em seguida, em didSelectRowAtIndexPath ()

  func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    self.tableView.deselectRowAtIndexPath(indexPath, animated: true)

    // To Get the Focus of CC, so that we can expand Bcc
    if self.cellTypes[indexPath.row] == CellType.Cc {

        if let cell = tableView.cellForRowAtIndexPath(indexPath) as? RecipientTableViewCell {

            if cell.tag == 1 {
                cell.recipientTypeLabel.text = "Cc:"
                cell.recipientTextField.userInteractionEnabled = true
                cell.recipientTextField.becomeFirstResponder()

                isCcBccSelected = true

                tableView.beginUpdates()
                tableView.endUpdates()
            }
        }
    }
}

1

Sim é possivel.

UITableView tem um método delegado didSelectRowAtIndexPath

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    [UIView animateWithDuration:.6
                          delay:0
         usingSpringWithDamping:UIViewAnimationOptionBeginFromCurrentState
          initialSpringVelocity:0
                        options:UIViewAnimationOptionBeginFromCurrentState animations:^{

                            cellindex = [NSIndexPath indexPathForRow:indexPath.row inSection:indexPath.section];
                            NSArray* indexArray = [NSArray arrayWithObjects:indexPath, nil];
                            [violatedTableView beginUpdates];
                            [violatedTableView reloadRowsAtIndexPaths:indexArray withRowAnimation:UITableViewRowAnimationAutomatic];
                            [violatedTableView endUpdates];
                        }
                     completion:^(BOOL finished) {
    }];
}

Mas no seu caso, se o usuário rolar e selecionar uma célula diferente, você precisará ter a última célula selecionada para diminuir e expandir as reloadRowsAtIndexPaths:chamadas de célula atualmente selecionadas , heightForRowAtIndexPath:portanto, manipule-as adequadamente.


0

Aqui está o meu código de UITableViewsubclasse personalizada , que se expande UITextViewna célula da tabela, sem recarregar (e perder o foco do teclado):

- (void)textViewDidChange:(UITextView *)textView {
    CGFloat textHeight = [textView sizeThatFits:CGSizeMake(self.width, MAXFLOAT)].height;
    // Check, if text height changed
    if (self.previousTextHeight != textHeight && self.previousTextHeight > 0) {
        [self beginUpdates];

        // Calculate difference in height
        CGFloat difference = textHeight - self.previousTextHeight;

        // Update currently editing cell's height
        CGRect editingCellFrame = self.editingCell.frame;
        editingCellFrame.size.height += difference;
        self.editingCell.frame = editingCellFrame;

        // Update UITableView contentSize
        self.contentSize = CGSizeMake(self.contentSize.width, self.contentSize.height + difference);

        // Scroll to bottom if cell is at the end of the table
        if (self.editingNoteInEndOfTable) {
            self.contentOffset = CGPointMake(self.contentOffset.x, self.contentOffset.y + difference);
        } else {
            // Update all next to editing cells
            NSInteger editingCellIndex = [self.visibleCells indexOfObject:self.editingCell];
            for (NSInteger i = editingCellIndex; i < self.visibleCells.count; i++) {
                UITableViewCell *cell = self.visibleCells[i];
                CGRect cellFrame = cell.frame;
                cellFrame.origin.y += difference;
                cell.frame = cellFrame;
            }
        }
        [self endUpdates];
    }
    self.previousTextHeight = textHeight;
}

0

Eu usei a resposta incrível do @ Joy, e funcionou perfeitamente com o iOS 8.4 e o XCode 7.1.1.

Caso você esteja procurando tornar seu celular compatível, alterei o -tableViewDidSelect para o seguinte:

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
//This is the bit I changed, so that if tapped once on the cell, 
//cell is expanded. If tapped again on the same cell, 
//cell is collapsed. 
    if (self.currentSelection==indexPath.row) {
        self.currentSelection = -1;
    }else{
        self.currentSelection = indexPath.row;
    }
        // animate
        [tableView beginUpdates];
        [tableView endUpdates];

}

Espero que isso ajude você.


0

Verifique este método após o iOS 7 e posterior.

- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath{
    return UITableViewAutomaticDimension;
}

Foram feitas melhorias no iOS 8. Podemos configurá-lo como propriedade da própria exibição de tabela.



0

Entradas -

tableView.beginUpdates () tableView.endUpdates () essas funções não chamarão

func tableView (_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {}

Mas, se o fizer, tableView.reloadRows (em: [selectedIndexPath! As IndexPath], com: .none)

Ele chamará o func tableView (_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {} nessa função.


-1

Eu apenas resolvi esse problema com um pequeno truque:

static int s_CellHeight = 30;
static int s_CellHeightEditing = 60;

- (void)onTimer {
    cellHeight++;
    [tableView reloadData];
    if (cellHeight < s_CellHeightEditing)
        heightAnimationTimer = [[NSTimer scheduledTimerWithTimeInterval:0.001 target:self selector:@selector(onTimer) userInfo:nil repeats:NO] retain];
}

- (CGFloat)tableView:(UITableView *)_tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
        if (isInEdit) {
            return cellHeight;
        }
        cellHeight = s_CellHeight;
        return s_CellHeight;
}

Quando preciso expandir a altura da célula, defino isInEdit = YESe chamo o método [self onTimer]e ele anima o crescimento da célula até atingir o valor s_CellHeightEditing :-)


No simulador funciona muito bem, mas no iPhone o hardware está lento. Com um atraso de 0,05 temporizador e com um aumento cellHeight de 5 unidades, é muito melhor, mas nada como CoreAnimation
Dzamir

11
Confirme, antes da postagem ... na próxima vez.
precisa saber é o seguinte

-2

Obtenha o caminho do índice da linha selecionada. Recarregue a tabela. No método heightForRowAtIndexPath de UITableViewDelegate, defina a altura da linha selecionada para uma altura diferente e, para os outros, retorne a altura normal da linha


2
-1, não funciona. Chamar os [table reloadData]resultados na mudança de altura acontece instantaneamente, em vez de animar.
Mark Amery
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.