UITableViewCell expande ao clicar

Vamos dizer que temos um UITableViewCell personalizado

Então, sempre que eu clico no botão personalizado na célula … ele deve se expandir até certo ponto (você pode dizer 40 de altura a mais …) e quando clico novamente no mesmo botão personalizado ele deve se reduzir à altura anterior.

Desenvolvedor, por favor, me guie. Como posso conseguir essa tarefa?

Implemente heightForRowAtIndexPath para calcular a altura correta. Em seguida, no código do seu botão, force a tabela a reavaliar a altura de cada célula com beginUpdates plus endUpdates:

[self.tableView beginUpdates]; [self.tableView endUpdates]; 

Alterações nas alturas das células tableview serão automaticamente calculadas com heightForRowAtIndexPath e as alterações serão animadas também.

Na verdade, em vez de um botão em seu celular que faz isso, você pode até mesmo fazer a seleção da célula fazer isso em didSelectRowAtIndexPath .

Não vou dizer nada aqui para contradizer a resposta aceita, considerando que está perfeitamente correto. No entanto, vou entrar em mais detalhes sobre como conseguir isso. Se você não quiser ler tudo isso e estiver mais interessado em brincar com o código-fonte em um projeto em funcionamento, enviei um projeto de exemplo para o GitHub .

A idéia básica é ter uma condição dentro do método -tableView: heightForRowAtIndexPath: que determina se a célula atual deve ou não ser expandida. Isso será acionado chamando atualizações de início / fim na tabela de dentro de -tableView: didSelectRowAtIndexPath: Neste exemplo, mostrarei como criar uma exibição de tabela que permita que uma célula seja expandida de cada vez.

A primeira coisa que você precisa fazer é declarar uma referência a um object NSIndexPath . Você pode fazer isso como quiser, mas eu recomendo usar uma declaração de propriedade como esta:

 @property (strong, nonatomic) NSIndexPath *expandedIndexPath; 

NOTA: Você não precisa criar este caminho de índice dentro de viewDidLoad ou qualquer outro método semelhante. O fato de o índice ser inicialmente nulo significará apenas que a tabela inicialmente não terá uma linha expandida. Se preferir que a tabela comece com uma linha de sua escolha expandida, você pode adicionar algo assim ao seu método viewDidLoad:

 NSInteger row = 1; NSInteger section = 2; self.expandedIndexPath = [NSIndexPath indexPathForRow:row inSection:section]; 

O próximo passo é seguir para o seu método UITableViewDelegate -tableView: didSelectRowAtIndexPath: para adicionar a lógica para alterar o índice da célula expandida com base na seleção de usuários. A ideia aqui é verificar o caminho do índice que acaba de ser selecionado em relação ao caminho do índice armazenado dentro da variável expandedIndexPath . Se os dois forem uma correspondência, saberemos que o usuário está tentando desmarcar a célula expandida. Nesse caso, definimos a variável como nula. Caso contrário, definimos a variável expandedIndexPath para o índice que acabou de ser selecionado. Tudo isso é feito entre as chamadas para beginUpdates / endUpdates, para permitir que a exibição de tabela manipule automaticamente a animação de transição.

 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { [tableView beginUpdates]; // tell the table you're about to start making changes // If the index path of the currently expanded cell is the same as the index that // has just been tapped set the expanded index to nil so that there aren't any // expanded cells, otherwise, set the expanded index to the index that has just // been selected. if ([indexPath compare:self.expandedIndexPath] == NSOrderedSame) { self.expandedIndexPath = nil; } else { self.expandedIndexPath = indexPath; } [tableView endUpdates]; // tell the table you're done making your changes } 

Em seguida, a etapa final está em outro método UITableViewDelegate -tableView: heightForRowAtIndexPath: Esse método será chamado depois que você acionou beginUpdates uma vez para cada caminho de índice que a tabela determina a atualização das necessidades. É aqui que você comparará o expandedIndexPath com o caminho do índice que está sendo reavaliado no momento.

Se os dois caminhos de índice são os mesmos, então esta é a célula que você deseja expandir, caso contrário, a altura deve ser normal. Eu usei os valores 100 e 44, mas você pode usar o que for adequado às suas necessidades.

 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { // Compares the index path for the current cell to the index path stored in the expanded // index path variable. If the two match, return a height of 100 points, otherwise return // a height of 44 points. if ([indexPath compare:self.expandedIndexPath] == NSOrderedSame) { return 100.0; // Expanded height } return 44.0; // Normal height } 

Eu criei uma biblioteca de código aberto para isso. Você apenas implementa o colapso e expande os delegates em seu código e voilà ! você também pode executar desenhos e animações. confira isso .

insira a descrição da imagem aqui

Eu fiz um componente reutilizável que fará exatamente o que você está falando. É muito fácil de usar e há um projeto de demonstração.

GCRetractableSectionController no GitHub.

Em vez de usar [tableView beginUpdates] e [tableView endUpdates] , estou usando o método [tableView reloadRowsAtIndexPath:... withRowAnimation:...] dentro do método didSelectRowAtIndexPath .

Eu prefiro isso, porque eu tive alguns problemas com elementos que devem mostrar, quando eu expandir meu UITableViewCell , quando eu usei os methods de atualizações de início e fim. Outro ponto é que você pode escolher entre algumas animações como: Top, Bottom, Left, Right …

Eu usei o código fonte do Gcamp e fiz minha própria versão.

1) Em um método loadView, inicialize um array mutável onde você salvará os estados expandidos ou não expandidos de suas seções. É fundamental salvar os status expandidos em uma matriz separada, que não é destruída enquanto a exibição de tabela rola (por exemplo, se você armazená-lo em um headerView, ele será redesenhado e esquecerá que o tempo foi expandido ou não). No meu caso, é array _sectionStatuses.

 - (void)loadView { // At the beginning all sections are expanded _sectionStates = [NSMutableArray arrayWithCapacity:self.tableView.numberOfSections]; for (int i = 0; i < self.tableView.numberOfSections; i++) { _sectionStates[i] = [NSNumber numberWithBool:YES]; } } 

2) Crie um headerView personalizado para uma seção com um botão para expansão. Delegar uma ação de um botão no seu headerView ao seu TableViewController usando o padrão de delegação. Você pode encontrar imagens adequadas no código-fonte do Gcamp.

3) Crie uma ação para remover ou adicionar linhas. Aqui _foldersArray é minha estrutura, que contém todos os dados. O headerView - MCExpandableAccountHeaderView da minha seção sabe seu próprio número de seção - eu o transferi para lá quando eu criei visualizações de header para cada seção. É fundamental transferi-lo para esse método, já que você precisa saber qual seção está expandida ou estendida.

 - (void)expandClicked:(MCAccountHeaderView *)sender { MCExpandableAccountHeaderView *expandableAccountHeaderView = (MCExpandableAccountHeaderView*)sender; // Finding a section, where a button was tapped NSInteger section = expandableAccountHeaderView.section; // Number of rows, that must be in a section when it is expanded NSUInteger contentCount = [_foldersArray[section - 1][@"folders"] count]; // Change a saved status of a section BOOL expanded = [_sectionStates[section] boolValue]; expanded = ! expanded; expandableAccountHeaderView.expanded = expanded; _sectionStates[section] = [NSNumber numberWithBool:expanded]; // Animation in a table [self.tableView beginUpdates]; NSMutableArray* modifiedIndexPaths = [[NSMutableArray alloc] init]; for (NSUInteger i = 0; i < contentCount; i++) { NSIndexPath* indexPath = [NSIndexPath indexPathForRow:i inSection:section]; [modifiedIndexPaths addObject:indexPath]; } if (expandableAccountHeaderView.expanded) [self.tableView insertRowsAtIndexPaths:modifiedIndexPaths withRowAnimation:UITableViewRowAnimationFade]; else [self.tableView deleteRowsAtIndexPaths:modifiedIndexPaths withRowAnimation:UITableViewRowAnimationFade]; [self.tableView endUpdates]; // Scroll to the top of current expanded section if (expandableAccountHeaderView.expanded) [self.tableView scrollToRowAtIndexPath:INDEX_PATH(0, section) atScrollPosition:UITableViewScrollPositionTop animated:YES]; } 

4) Também é importante retornar o número ou linhas corretos em uma seção, dependendo de quando ela é expandida ou não.

 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { BOOL expanded = [_sectionStates[section] boolValue]; return expanded ? [_foldersArray[section - 1][@"folders"] count] : 0; } 

Esta é a resposta de Mick, mas para o Swift 4. (O IndexPath substitui o NSIndexPath, que vem com um IndexPath vazio, já que nada desmoronaria no Swift. Além disso, você pode comparar duas instâncias do IndexPath usando == )

Declare a propriedade expandedIndexPath.

 var expandedIndexPath = IndexPath() 

Peça opcional viewDidLoad.

 expandedIndexPath = IndexPath(row: 1, section: 2) 

Então a parte didSelectRow.

 override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.beginUpdates() if indexPath == expandedIndexPath { expandedIndexPath = IndexPath() } else { expandedIndexPath = indexPath } tableView.endUpdates() } 

Então a parte heightForRow.

 override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { if indexPath == expandedIndexPath { return 100 } return 44 } 
 initialize iSelectedIndex = -1; and declare UITableView *urTableView; - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{ return 10; //Section count } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return 3; //row count } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"Cell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; if(cell == nil) { cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease]; } [cell.textLabel setText:[NSString stringWithFormat:@"sec:%d,row:%d",indexPath.section,indexPath.row]]; return cell; } - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{ // adding a label with the tap gesture to the header in each section headerLabel = [[UILabel alloc]init]; headerLabel.tag = section; headerLabel.userInteractionEnabled = YES; headerLabel.backgroundColor = [UIColor greenColor]; headerLabel.text = [NSString stringWithFormat:@"Header No.%d",section]; headerLabel.frame = CGRectMake(0, 0, tableView.tableHeaderView.frame.size.width, tableView.tableHeaderView.frame.size.height); UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(gestureTapped:)]; [headerLabel addGestureRecognizer:tapGesture]; return headerLabel; } - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{ return 50.0; //adjust the height as you need } - (void)gestureTapped:(UITapGestureRecognizer *)sender{ UIView *theSuperview = self.view; // whatever view contains CGPoint touchPointInSuperview = [sender locationInView:theSuperview]; UIView *touchedView = [theSuperview hitTest:touchPointInSuperview withEvent:nil]; if([touchedView isKindOfClass:[UILabel class]]) { if (iSelectedIndex != touchedView.tag) { //if new header is selected , need to expand iSelectedIndex = touchedView.tag; }else{ // if the header is already expanded , need to collapse iSelectedIndex = -1; } [urTableView beginUpdates]; [urTableView endUpdates]; } } - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { // Show or hide cell float height = 0.0; if (indexPath.section == iSelectedIndex) { height = 44.0; // Show the cell - adjust the height as you need } return height; } 

Para adicionar à resposta 0x7fffffff , descobri que precisava de uma condição extra na instrução if em didSelectRowAtIndexPath – assim:

 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { [tableView beginUpdates]; if (self.expandedIndexPath && [indexPath compare:self.expandedIndexPath] == NSOrderedSame) { self.expandedIndexPath = nil; } else { self.expandedIndexPath = indexPath; } [tableView endUpdates]; }