UITableView definido como células estáticas. É possível ocultar algumas das células programaticamente?

132

UITableView definido para células estáticas.

É possível ocultar algumas das células programaticamente?

Shmidt
fonte

Respostas:

48

Você está procurando esta solução:

StaticDataTableViewController 2.0

https://github.com/xelvenone/StaticDataTableViewController

que pode mostrar / ocultar / recarregar qualquer célula estática com ou sem animação!

[self cell:self.outletToMyStaticCell1 setHidden:hide]; 
[self cell:self.outletToMyStaticCell2 setHidden:hide]; 
[self reloadDataAnimated:YES];

Nota para sempre usar somente (reloadDataAnimated: YES / NO) (não chame [self.tableView reloadData] diretamente)

Isso não usa a solução hacky com altura de 0 e permite animar a alteração e ocultar seções inteiras

Peter Lapisu
fonte
6
Um problema dessa solução é que ela deixa lacunas onde estavam as células ocultas.
Jack
como você quer dizer lacunas? você pode descrever melhor a situação, talvez no github diretamente? você executou a amostra?
Peter Lapisu
1
Quero dizer que, se você tem, por exemplo, três seções e oculta as células da seção do meio, permanece o espaço em que essa seção estava. Não tentei o projeto de amostra, mas tentei o código. Vou tentar a amostra.
Jack
1
confira a amostra, houve uma atualização maior, talvez você teve algum código antigo ... funciona perfeito para mim
Peter Lapisu
Eu tentei a amostra e parece funcionar lá, a única coisa que estou fazendo diferente é usar um, IBOutletCollectionmas não vejo como isso faria diferença. Eu só baixei o código ontem, então não acho que seja uma versão antiga.
Jack
164

Para ocultar células estáticas no UITable:

  1. Adicione este método :

Na classe delegada do controlador UITableView:

Objetivo-C:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell* cell = [super tableView:tableView cellForRowAtIndexPath:indexPath];

    if(cell == self.cellYouWantToHide)
        return 0; //set the hidden cell's height to 0

    return [super tableView:tableView heightForRowAtIndexPath:indexPath];
}

Rápido:

override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    var cell = super.tableView(tableView, cellForRowAtIndexPath: indexPath)

    if cell == self.cellYouWantToHide {
        return 0
    }

    return super.tableView(tableView, heightForRowAtIndexPath: indexPath)
}

Este método será chamado para cada célula na UITable. Depois que a chama para a célula que você deseja ocultar, definimos sua altura como 0. Identificamos a célula de destino criando uma saída para ela:

  1. No designer, crie uma saída para a (s) célula (s) que você deseja ocultar. A saída para uma dessas células é chamada "cellYouWantToHide" acima.
  2. Marque "Subvisões de Clipe" no IB para as células que você deseja ocultar. As células que você está ocultando precisam ter ClipToBounds = YES. Caso contrário, o texto será empilhado no UITableView.
Assim como
fonte
10
Ótima solução. Para evitar o ClipToBoundsproblema, você também pode definir a célula como oculta. Isso me pareceria mais limpo. ;-)
MonsieurDez
13
+1 para ClipToBounds = YES. As soluções da Numeorus em outros segmentos perderam isso.
Jeff
2
Boa ideia. Esta é a solução mais fácil que encontrei. Outras soluções tinham código em dois ou mais locais. BTW, no IB, ClipToBounds está listado como "Clip Subviews".
VaporwareWolf
1
Apenas uma pequena correção, use self .cellYouWantToHide em vez desta .cellYouWantToHide.
Zoltan Vinkler
2
sem criar um objeto de célula, também podemos usar "index.row" para ocultar a "uitableviewcell"
g212gs
38

A melhor maneira é descrita no seguinte blog http://ali-reynolds.com/2013/06/29/hide-cells-in-static-table-view/

Projete sua visualização de tabela estática normalmente no criador de interface - completo com todas as células potencialmente ocultas. Mas há uma coisa que você deve fazer para cada célula em potencial que deseja ocultar - marque a propriedade “Clip subviews” da célula, caso contrário, o conteúdo da célula não desaparece quando você tenta ocultá-la (diminuindo sua altura - mais tarde).

SO - você tem um comutador em uma célula e o comutador deve ocultar e mostrar algumas células estáticas. Conecte-o a uma IBAction e faça isso:

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

Isso fornece animações agradáveis ​​para as células que aparecem e desaparecem. Agora implemente o seguinte método delegado de exibição de tabela:

- (float)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    if (indexPath.section == 1 && indexPath.row == 1) { // This is the cell to hide - change as you need
    // Show or hide cell
        if (self.mySwitch.on) {
            return 44; // Show the cell - adjust the height as you need
        } else {
            return 0; // Hide the cell
        }
   }
   return 44;
}

E é isso. Gire o botão e a célula se oculta e reaparece com uma animação agradável e suave.

Mohamed Saleh
fonte
Atualização: você também precisa definir o rótulo da célula como Ocultar / Mostrar com você.
Mohamed Saleh
1
Você deve observar que, se qualquer UIView no conteúdo de sua célula estática tiver alguma restrição em relação ao cell.content, você poderá receber um erro de tempo de execução se essas restrições forem inválidas para a nova altura da célula.
Pedro Borges
1
Melhor resposta. Obrigado.
Eric Chen
1
Eu sabia que esse truque funcionava com conteúdo dinâmico. Funciona muito bem com estática também. Tão simples também.
Formigas
2
Este realmente funciona, mas para cobrir um ponto que falta: se a linha a ser oculta / mostrada contiver um seletor de datas, faça apenas [self.tableView beginUpdates]; [self.tableView endUpdates]; ainda deixará uma falha enquanto a oculta. Para eliminar a falha, você deve chamar [self.tableView reloadRowsAtIndexPaths: @ [indexPathOfTargetRow] withRowAnimation: UITableViewRowAnimationNone]; Observe também que a animação de linha está de alguma forma "desativada" aqui.
Chris
34

Minha solução segue uma direção semelhante à de Gareth, embora eu faça algumas coisas de maneira diferente.

Aqui vai:

1. Esconda as células

Não há como ocultar diretamente as células. UITableViewControlleré a fonte de dados que fornece as células estáticas e, atualmente, não há como dizer "não forneça a célula x". Portanto, precisamos fornecer nossa própria fonte de dados, que delega para a UITableViewControllerfim de obter as células estáticas.

O mais fácil é subclassificar UITableViewControllere substituir todos os métodos que precisam se comportar de maneira diferente ao ocultar células .

No caso mais simples (tabela de seção única, todas as células têm a mesma altura), isso seria assim:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section    
{
    return [super tableView:tableView numberOfRowsInSection:section] - numberOfCellsHidden;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Recalculate indexPath based on hidden cells
    indexPath = [self offsetIndexPath:indexPath];

    return [super tableView:tableView cellForRowAtIndexPath:indexPath];
}

- (NSIndexPath*)offsetIndexPath:(NSIndexPath*)indexPath
{
    int offsetSection = indexPath.section; // Also offset section if you intend to hide whole sections
    int numberOfCellsHiddenAbove = ... // Calculate how many cells are hidden above the given indexPath.row
    int offsetRow = indexPath.row + numberOfCellsHiddenAbove;

    return [NSIndexPath indexPathForRow:offsetRow inSection:offsetSection];
}

Se sua tabela tiver várias seções ou as células tiverem alturas diferentes, será necessário substituir mais métodos. O mesmo princípio se aplica aqui: você precisa compensar indexPath, seção e linha antes de delegar para super.

Lembre-se também de que o parâmetro indexPath para métodos como didSelectRowAtIndexPath:será diferente para a mesma célula, dependendo do estado (ou seja, o número de células ocultas). Portanto, é provavelmente uma boa ideia sempre compensar qualquer parâmetro indexPath e trabalhar com esses valores.

2. Animar a mudança

Como Gareth já afirmou, você obtém grandes falhas se animar alterações usando o reloadSections:withRowAnimation:método

Eu descobri que, se você ligar reloadData:imediatamente depois, a animação será muito melhorada (apenas pequenas falhas). A tabela é exibida corretamente após a animação.

Então, o que estou fazendo é:

- (void)changeState
{
     // Change state so cells are hidden/unhidden
     ...

    // Reload all sections
    NSIndexSet* reloadSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [self numberOfSectionsInTableView:tableView])];

    [tableView reloadSections:reloadSet withRowAnimation:UITableViewRowAnimationAutomatic];
    [tableView reloadData];
}
henning77
fonte
2
Abençoe você, sua doce pessoa.
precisa saber é
você encontrou uma solução para as pequenas falhas de animação? para mim, as linhas separadoras entre as células não serão animadas corretamente e eu não prefiro usar nenhuma animação do que uma gliched. ótima solução!
Maximilian Körner
Meu problema é que com células estáticas numberOfRowsInSection:é chamado somente quando a tabela é carregada pela primeira vez. Quando eu chamo [self.tableView reloadData] - numberOfRowsInSection:nunca é chamado novamente. Apenas cellForRowAtIndexPath:é chamado. o que estou perdendo?
Roman
13
  1. No designer, crie uma saída para a (s) célula (s) que você deseja ocultar. Por exemplo, você deseja ocultar 'cellOne', portanto, em viewDidLoad (), faça isso

cellOneOutlet.hidden = true

agora substitua o método abaixo, verifique qual status da célula está oculto e retorne a altura 0 para essas células. Essa é uma das muitas maneiras pelas quais você pode ocultar qualquer célula no tableView estático rapidamente.

override func tableView(tableView: UITableView, heightForRowAtIndexPathindexPath: NSIndexPath) -> CGFloat 
{

let tableViewCell = super.tableView(tableView,cellForRowAtIndexPath: indexPath)

        if tableViewCell.hidden == true
        {
            return 0
        }
        else{
             return super.tableView(tableView, heightForRowAtIndexPath: indexPath)
        }

}
Saleh Masum
fonte
Melhor solução até agora!
Derdida 20/09/16
adicione tableView.reloadData () depois de fazer alterações e é perfeito. Economizei muito esforço para mudar a solução! graças
Shayan C
1
Swift 4 não me deixa usar let tableViewCell = super.tableView(tableView,cellForRowAtIndexPath: indexPath). Presumo que seja substituído por let tableViewCell = tableView.cellForRow(at: indexPath as IndexPath).
Clifton Labrum
Como estou usando células estáticas em a UITableViewController, não tenho UITableViewmétodos de delegação. Para que ele seja chamado heightForRow, também preciso ter outros métodos?
Clifton Labrum
@CliftonLabrum não, você pode substituir apenas esse método.
Alexander Ershov
11

Eu vim com uma alternativa que realmente oculta seções e não as exclui. Tentei a abordagem do @ henning77, mas continuava tendo problemas quando alterava o número de seções do UITableView estático. Esse método funcionou muito bem para mim, mas estou tentando principalmente ocultar seções em vez de linhas. Estou removendo algumas linhas rapidamente, mas é muito mais confuso, então tentei agrupar as coisas em seções que preciso mostrar ou ocultar. Aqui está um exemplo de como estou ocultando seções:

Primeiro declaro uma propriedade NSMutableArray

@property (nonatomic, strong) NSMutableArray *hiddenSections;

No viewDidLoad (ou depois de consultar seus dados), você pode adicionar seções que deseja ocultar à matriz.

- (void)viewDidLoad
{
    hiddenSections = [NSMutableArray new];

    if(some piece of data is empty){
        // Add index of section that should be hidden
        [self.hiddenSections addObject:[NSNumber numberWithInt:1]];
    }

    ... add as many sections to the array as needed

    [self.tableView reloadData];
}

Em seguida, implemente os seguintes métodos de delegação do TableView

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
    if([self.hiddenSections containsObject:[NSNumber numberWithInt:section]]){
        return nil;
    }

    return [super tableView:tableView titleForHeaderInSection:section];
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    if([self.hiddenSections containsObject:[NSNumber numberWithInt:indexPath.section]]){
        return 0;
    }

    return [super tableView:tableView heightForRowAtIndexPath:[self offsetIndexPath:indexPath]];
}

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
    if([self.hiddenSections containsObject:[NSNumber numberWithInt:indexPath.section]]){
        [cell setHidden:YES];
    }
}

Em seguida, defina a altura do cabeçalho e rodapé como 1 para seções ocultas porque você não pode definir a altura como 0. Isso causa um espaço adicional de 2 pixels, mas podemos compensar isso ajustando a altura do próximo cabeçalho visível.

-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section 
{
    CGFloat height = [super tableView:tableView heightForHeaderInSection:section];

    if([self.hiddenSections containsObject:[NSNumber numberWithInt:section]]){
        height = 1; // Can't be zero
    }
    else if([self tableView:tableView titleForHeaderInSection:section] == nil){ // Only adjust if title is nil
        // Adjust height for previous hidden sections
        CGFloat adjust = 0;

        for(int i = (section - 1); i >= 0; i--){
            if([self.hiddenSections containsObject:[NSNumber numberWithInt:i]]){
                adjust = adjust + 2;
            }
            else {
                break;
            }
        }

        if(adjust > 0)
        {                
            if(height == -1){
                height = self.tableView.sectionHeaderHeight;
            }

            height = height - adjust;

            if(height < 1){
                height = 1;
            }
        }
    }

    return height;
}

-(CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section 
{   
    if([self.hiddenSections containsObject:[NSNumber numberWithInt:section]]){
        return 1;
    }
    return [super tableView:tableView heightForFooterInSection:section];
}

Em seguida, se você tiver linhas específicas para ocultar, poderá ajustar o numberOfRowsInSection e quais linhas serão retornadas em cellForRowAtIndexPath. Neste exemplo, aqui eu tenho uma seção que possui três linhas em que três podem estar vazias e precisam ser removidas.

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    NSInteger rows = [super tableView:tableView numberOfRowsInSection:section];

    if(self.organization != nil){
        if(section == 5){ // Contact
            if([self.organization objectForKey:@"Phone"] == [NSNull null]){     
                rows--;
            }

            if([self.organization objectForKey:@"Email"] == [NSNull null]){     
                rows--;
            }

            if([self.organization objectForKey:@"City"] == [NSNull null]){     
                rows--;
            }
        }
    }

    return rows;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{    
    return [super tableView:tableView cellForRowAtIndexPath:[self offsetIndexPath:indexPath]];
}

Use este offsetIndexPath para calcular o indexPath para linhas nas quais você está removendo condicionalmente as linhas. Não é necessário se você estiver ocultando apenas seções

- (NSIndexPath *)offsetIndexPath:(NSIndexPath*)indexPath
{
    int row = indexPath.row;

    if(self.organization != nil){
        if(indexPath.section == 5){
            // Adjust row to return based on which rows before are hidden
            if(indexPath.row == 0 && [self.organization objectForKey:@"Phone"] == [NSNull null] && [self.organization objectForKey:@"Email"] != [NSNull null]){     
                row++;
            }
            else if(indexPath.row == 0 && [self.organization objectForKey:@"Phone"] == [NSNull null] && [self.organization objectForKey:@"Address"] != [NSNull null]){     
                row = row + 2;
            }
            else if(indexPath.row == 1 && [self.organization objectForKey:@"Phone"] != [NSNull null] && [self.organization objectForKey:@"Email"] == [NSNull null]){     
                row++;
            }
            else if(indexPath.row == 1 && [self.organization objectForKey:@"Phone"] == [NSNull null] && [self.organization objectForKey:@"Email"] != [NSNull null]){     
                row++;
            }
        }
    }

    NSIndexPath *offsetPath = [NSIndexPath indexPathForRow:row inSection:indexPath.section];

    return offsetPath;
}

Existem muitos métodos a serem substituídos, mas o que eu gosto nessa abordagem é que ela é reutilizável. Configure a matriz hiddenSections, adicione-a e ela ocultará as seções corretas. Escondendo as linhas é um pouco mais complicado, mas possível. Não podemos simplesmente definir a altura das linhas que queremos ocultar para 0 se estivermos usando um UITableView agrupado porque as bordas não serão desenhadas corretamente.

Austin
fonte
1
Eu provavelmente usaria NSMutableSetem hiddenSectionsvez disso. É muito mais rápido, pois você está testando principalmente a associação.
Pixelfreak
Boa resposta, Austin. Votei a resposta e usarei isso como referência para meus projetos no futuro. O pixelfreak também faz um bom argumento sobre o uso NSMutableSetde hiddenSections, embora eu entenda que o ponto da sua resposta foi mais conceitual do que exigente quanto ao tipo de estrutura de dados que você deve usar.
BigSauce
10

Acontece que você pode ocultar e mostrar células em um UITableView estático - e com animação. E não é tão difícil de realizar.

Projeto de demonstração

Vídeo do projeto de demonstração

A essência:

  1. Use tableView:heightForRowAtIndexPath: para especificar alturas de célula dinamicamente com base em algum estado.
  2. Quando o estado muda, animar células mostrando / ocultando chamando tableView.beginUpdates();tableView.endUpdates()
  3. Não ligue para tableView.cellForRowAtIndexPath:dentro tableView:heightForRowAtIndexPath:. Use indexPaths em cache para diferenciar as células.
  4. Não esconda células. Defina a propriedade "Clip Subviews" no Xcode.
  5. Use células personalizadas (não planas, etc.) para obter uma boa animação oculta. Além disso, manuseie o Layout automático corretamente para o caso em que a altura da célula == 0.

Mais informações no meu blog (idioma russo)

Sergey Skoblikov
fonte
1
Este ponto foi importante para mim: "Não oculte células. Configure a propriedade" Clip Subviews "no Xcode."
balkoth
8

Sim, é definitivamente possível, embora eu esteja lutando com o mesmo problema no momento. Consegui fazer com que as células se escondessem e tudo funcionasse bem, mas atualmente não posso fazer a coisa animar perfeitamente. Aqui está o que eu encontrei:

Estou ocultando linhas com base no estado de um botão ON / OFF na primeira linha da primeira seção. Se o interruptor estiver LIGADO, há 1 linha abaixo na mesma seção, caso contrário, existem 2 linhas diferentes.

Eu tenho um seletor chamado quando o comutador é alternado e defino uma variável para indicar em que estado estou. Depois, chamo:

[[self tableView] reloadData];

Eu substituo a função tableView: willDisplayCell: forRowAtIndexPath: e se a célula deveria estar oculta, faço isso:

[cell setHidden:YES];

Isso oculta a célula e seu conteúdo, mas não remove o espaço que ocupa.

Para remover o espaço, substitua a função tableView: heightForRowAtIndexPath: e retorne 0 para as linhas que devem estar ocultas.

Você também precisa substituir tableView: numberOfRowsInSection: e retornar o número de linhas nessa seção. Você precisa fazer algo estranho aqui para que, se sua tabela for um estilo agrupado, os cantos arredondados ocorram nas células corretas. Na minha tabela estática, há o conjunto completo de células para a seção; portanto, há a primeira célula que contém a opção, depois uma célula para as opções de estado ON e mais duas células para as opções de estado OFF, um total de 4 células. Quando a opção está ativada, tenho que retornar 4, isso inclui a opção oculta para que a última opção exibida tenha uma caixa arredondada. Quando a opção está desativada, as duas últimas opções não são exibidas, então eu retorno 2. Isso tudo parece desajeitado. Desculpe se isso não está muito claro, é difícil de descrever. Apenas para ilustrar a configuração, esta é a construção da seção da tabela no IB:

  • Linha 0: Opção com interruptor ON / OFF
  • Linha 1: exibida quando a opção está ativada
  • Linha 2: exibida quando a opção está desativada
  • Linha 3: exibida quando a opção está desativada

Portanto, quando a opção está ativada, a tabela relata duas linhas que são:

  • Linha 0: Opção com interruptor ON / OFF
  • Linha 1: exibida quando a opção está ativada

Quando a opção está DESLIGADA, a tabela reporta quatro linhas:

  • Linha 0: Opção com interruptor ON / OFF
  • Linha 1: exibida quando a opção está ativada
  • Linha 2: exibida quando a opção está desativada
  • Linha 3: exibida quando a opção está desativada

Essa abordagem não parece correta por várias razões, é o máximo que eu tenho com a minha experiência até agora; então, deixe-me saber se você encontrar uma maneira melhor. Os problemas que observei até agora são:

  • Parece errado dizer à tabela que o número de linhas é diferente do que está presumivelmente contido nos dados subjacentes.

  • Não consigo animar a mudança. Eu tentei usar tableView: reloadSections: withRowAnimation: em vez de reloadData e os resultados não parecem fazer sentido, ainda estou tentando fazer isso funcionar. Atualmente, o que parece acontecer é que o tableView não atualiza as linhas corretas, permanecendo oculto que deve ser exibido e um nulo é deixado na primeira linha. Eu acho que isso pode estar relacionado ao primeiro ponto sobre os dados subjacentes.

Espero que alguém possa sugerir métodos alternativos ou talvez como estender a animação, mas talvez isso o ajude a começar. Minhas desculpas pela falta de hiperlinks para funções, eu os coloquei, mas eles foram rejeitados pelo filtro de spam porque eu sou um usuário relativamente novo.

Gareth Clarke
fonte
Tenho certeza que você deve chamar [[self tableView] reloadData];mais uma vez depois de esconder as células
Shmidt
Desculpe reviver isso, mas como você conseguiu subclassificar UITableViewController e usá-lo com células estáticas? Xcode não vai me deixar.
Dany Joumaa
Não, você não precisa subclassificar UITableView. No storyboard, escolha seu TableViewController e clique em TableView. Agora você pode selecionar entre células dinâmicas / estáticas.
Shmidt
1
Eu encontrei a solução como animar células estáticas esconder / mostrar. Basta ligar return [super tableView:tableView cellForRowAtIndexPath:indexPath];na UITableViewControllersubclasse para a criação de células estáticas. Caminhos de índice são indexados estático - não dinâmica ...
k06a
8

Conforme resposta de Justas, mas para Swift 4:

override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    let cell = super.tableView(tableView, cellForRowAt: indexPath)

    if cell == self.cellYouWantToHide {
        return 0
    }

    return super.tableView(tableView, heightForRowAt: indexPath)
}
Chris Herbst
fonte
1
pode ser necessário tableView.reloadRows(at:, with:)atualizar as células se você alterar a altura enquanto a linha já estiver visível.
Frost-Lee
5

Ok, depois de algumas tentativas, tenho uma resposta não comum. Estou usando a variável "isHidden" ou "hidden" para verificar se esta célula deve estar oculta.

  1. crie um IBOutlet para o seu controlador de exibição. @IBOutlet weak var myCell: UITableViewCell!

  2. Atualize o myCellem sua função personalizada, por exemplo, você pode adicioná-lo em viewDidLoad:

override func viewDidLoad() { super.viewDidLoad() self.myCell.isHidden = true }

  1. no seu método de delegação:

override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { let cell = super.tableView(tableView, cellForRowAt: indexPath) guard !cell.isHidden else { return 0 } return super.tableView(tableView, heightForRowAt: indexPath) }

Isso reduzirá sua lógica no método delegado, e você só precisará se concentrar nos requisitos de negócios.

BananZ
fonte
4

As respostas acima que ocultam / mostram células, alteram rowHeight ou interferem nas restrições de layout automático não funcionaram para mim devido a problemas de layout automático. O código tornou-se intolerável.

Para uma tabela estática simples, o que funcionou melhor para mim foi:

  1. Crie uma saída para cada célula na tabela estática
  2. Crie uma matriz apenas com saídas de células para mostrar
  3. Substitua cellForRowAtIndexPath para retornar a célula da matriz
  4. Substitua numberOfRowsInSection para retornar a contagem da matriz
  5. Implemente um método para determinar quais células precisam estar nessa matriz e chame esse método sempre que necessário e, em seguida, reloadData.

Aqui está um exemplo do meu controlador de exibição de tabela:

@IBOutlet weak var titleCell: UITableViewCell!
@IBOutlet weak var nagCell: UITableViewCell!
@IBOutlet weak var categoryCell: UITableViewCell!

var cellsToShow: [UITableViewCell] = []

override func viewDidLoad() {
    super.viewDidLoad()
    determinCellsToShow()
}

func determinCellsToShow() {
    if detail!.duration.type != nil {
        cellsToShow = [titleCell, nagCell, categoryCell]
    }
    else {
        cellsToShow = [titleCell,  categoryCell]
    }
}

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    return cellsToShow[indexPath.row]
}

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return cellsToShow.count
}
David Barta
fonte
Isso funcionou muito bem, mesmo 5 anos depois! Muito obrigado.
Schrockwell 23/03
2020 e esta é a melhor solução para isso. Não é necessário hackear.
mdonati 6/04
3

IOS 11 simples e método compatível com IB / Storyboard

Para o iOS 11, descobri que uma versão modificada da resposta de Mohamed Saleh funcionava melhor, com algumas melhorias baseadas na documentação da Apple. Ele é animado, evita hacks feios ou valores codificados e usa alturas de linha já definidas no Interface Builder .

O conceito básico é definir a altura da linha como 0 para todas as linhas ocultas. Em seguida, use tableView.performBatchUpdatespara acionar uma animação que funcione de forma consistente.

Defina as alturas das células

override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    if indexPath == indexPathOfHiddenCell {
        if cellIsHidden {
            return 0
        }
    }
    // Calling super will use the height set in your storyboard, avoiding hardcoded values
    return super.tableView(tableView, heightForRowAt: indexPath)
}

Você quer ter certeza cellIsHiddene indexPathOfHiddenCellestá configurado adequadamente para seu caso de uso. Para o meu código, são propriedades no meu controlador de exibição de tabela.

Alternando a célula

Seja qual for o método que controla a visibilidade (provavelmente uma ação de botão ou didSelectRow), alterne o estado cellIsHidden, dentro de um performBatchUpdatesbloco:

tableView.performBatchUpdates({
                // Use self to capture for block
                self.cellIsHidden = !self.cellIsHidden 
            }, completion: nil)

A Apple recomenda performBatchUpdatesmais beginUpdates/endUpdates sempre que possível.

robmathers
fonte
1

Encontrei uma solução para animar células ocultas na tabela estática.

// Class for wrapping Objective-C block
typedef BOOL (^HidableCellVisibilityFunctor)();
@interface BlockExecutor : NSObject
@property (strong,nonatomic) HidableCellVisibilityFunctor block;
+ (BlockExecutor*)executorWithBlock:(HidableCellVisibilityFunctor)block;
@end
@implementation BlockExecutor
@synthesize block = _block;
+ (BlockExecutor*)executorWithBlock:(HidableCellVisibilityFunctor)block
{
    BlockExecutor * executor = [[BlockExecutor alloc] init];
    executor.block = block;
    return executor;
}
@end

Apenas um dicionário adicional é necessário:

@interface MyTableViewController ()
@property (nonatomic) NSMutableDictionary * hidableCellsDict;
@property (weak, nonatomic) IBOutlet UISwitch * birthdaySwitch;
@end

E observe a implementação do MyTableViewController. Precisamos de dois métodos para converter indexPath entre índices visíveis e invisíveis ...

- (NSIndexPath*)recoverIndexPath:(NSIndexPath *)indexPath
{
    int rowDelta = 0;
    for (NSIndexPath * ip in [[self.hidableCellsDict allKeys] sortedArrayUsingSelector:@selector(compare:)])
    {
        BlockExecutor * executor = [self.hidableCellsDict objectForKey:ip];
        if (ip.section == indexPath.section
            && ip.row <= indexPath.row + rowDelta
            && !executor.block())
        {
            rowDelta++;
        }
    }
    return [NSIndexPath indexPathForRow:indexPath.row+rowDelta inSection:indexPath.section];
}

- (NSIndexPath*)mapToNewIndexPath:(NSIndexPath *)indexPath
{
    int rowDelta = 0;
    for (NSIndexPath * ip in [[self.hidableCellsDict allKeys] sortedArrayUsingSelector:@selector(compare:)])
    {
        BlockExecutor * executor = [self.hidableCellsDict objectForKey:ip];
        if (ip.section == indexPath.section
            && ip.row < indexPath.row - rowDelta
            && !executor.block())
        {
            rowDelta++;
        }
    }
    return [NSIndexPath indexPathForRow:indexPath.row-rowDelta inSection:indexPath.section];
}

Uma alteração da IBAction no valor do UISwitch:

- (IBAction)birthdaySwitchChanged:(id)sender
{
    NSIndexPath * indexPath = [self mapToNewIndexPath:[NSIndexPath indexPathForRow:1 inSection:1]];
    if (self.birthdaySwitch.on)
        [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
    else
        [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}

Alguns métodos UITableViewDataSource e UITableViewDelegate:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    int numberOfRows = [super tableView:tableView numberOfRowsInSection:section];
    for (NSIndexPath * indexPath in [self.hidableCellsDict allKeys])
        if (indexPath.section == section)
        {
            BlockExecutor * executor = [self.hidableCellsDict objectForKey:indexPath];
            numberOfRows -= (executor.block()?0:1);
        }
    return numberOfRows;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    indexPath = [self recoverIndexPath:indexPath];
    return [super tableView:tableView cellForRowAtIndexPath:indexPath];
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    indexPath = [self recoverIndexPath:indexPath];
    return [super tableView:tableView heightForRowAtIndexPath:indexPath];
}

- (void)viewDidLoad
{
    [super viewDidLoad];

    // initializing dictionary
    self.hidableCellsDict = [NSMutableDictionary dictionary];
    [self.hidableCellsDict setObject:[BlockExecutor executorWithBlock:^(){return self.birthdaySwitch.on;}] forKey:[NSIndexPath indexPathForRow:1 inSection:1]];
}

- (void)viewDidUnload
{
    [self setBirthdaySwitch:nil];
    [super viewDidUnload];
}

@end
k06a
fonte
1

Resposta rápida :

Adicione o seguinte método no seu TableViewController:

override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    return indexPathOfCellYouWantToHide == indexPath ? 0 : super.tableView(tableView, heightForRowAtIndexPath: indexPath)
}

se o tableView tentar desenhar a célula que você deseja ocultar, ela não será exibida porque sua altura será definida como 0pt, graças ao método acima, todo o resto permanecerá inalterado.

Observe que indexPathOfCellYouWantToHidepode ser alterado a qualquer momento :)

Federico Zanetello
fonte
1

No Swift 2.2, eu combinei algumas respostas aqui.

Faça uma saída do storyboard para vincular ao seu staticCell.

@IBOutlet weak var updateStaticCell: UITableViewCell!

override func viewDidLoad() {
    ...
    updateStaticCell.hidden = true
}

override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    if indexPath.row == 0 {
        return 0
    } else {
        return super.tableView(tableView, heightForRowAtIndexPath: indexPath)
    }
}

Quero ocultar minha primeira célula e defino a altura como 0, conforme descrito acima.

CodeOverRide
fonte
1

Swift 4:

override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    var height = super.tableView(tableView, heightForRowAt: indexPath)
    if (indexPath.row == HIDDENROW) {
        height = 0.0
    }
    return height
}
pizzamonster
fonte
0

Para o cenário mais fácil ao ocultar células na parte inferior da exibição da tabela, você pode ajustar o contentInset do tableView após ocultar a célula:

- (void)adjustBottomInsetForHiddenSections:(NSInteger)numberOfHiddenSections
{
    CGFloat bottomInset = numberOfHiddenSections * 44.0; // or any other 'magic number
    self.tableView.contentInset = UIEdgeInsetsMake(self.tableView.contentInset.top, self.tableView.contentInset.left, -bottomInset, self.tableView.contentInset.right);
}
Vladimir Shutyuk
fonte
0

Esta é uma nova maneira de fazer isso usando https://github.com/k06a/ABStaticTableViewController

NSIndexPath *ip = [NSIndexPath indexPathForRow:1 section:1];
[self deleteRowsAtIndexPaths:@[ip] withRowAnimation:UITableViewRowAnimationFade]
k06a
fonte
0

A solução do k06a ( https://github.com/k06a/ABStaticTableViewController ) é melhor porque oculta toda a seção, incluindo cabeçalhos e rodapés de células, onde esta solução ( https://github.com/peterpaulis/StaticDataTableViewController ) oculta tudo, exceto o rodapé.

EDITAR

Acabei de encontrar uma solução, se você deseja ocultar o rodapé StaticDataTableViewController. É isso que você precisa copiar no arquivo StaticTableViewController.m:

- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section {
    if ([tableView.dataSource tableView:tableView numberOfRowsInSection:section] == 0) {
        return nil;
    } else {
        return [super tableView:tableView titleForFooterInSection:section];
    }
}

- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {

    CGFloat height = [super tableView:tableView heightForFooterInSection:section];

    if (self.originalTable == nil) {
        return height;
    }

    if (!self.hideSectionsWithHiddenRows) {
        return height;
    }

    OriginalSection * os = self.originalTable.sections[section];
    if ([os numberOfVissibleRows] == 0) {
       //return 0;
        return CGFLOAT_MIN;
    } else {
        return height;
    }

    //return 0;
    return CGFLOAT_MIN;
}
pawel221
fonte
0

Certamente você pode. Primeiro, retorne ao número do número de células que você deseja exibir no tableView e ligue superpara obter determinada célula do storyboard e devolva-a para o tableView:

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return self.mode.numberOfCells()
}

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = super.tableView(tableView, cellForRowAtIndexPath: self.mode.indexPathForIndexPath(indexPath))

    return cell
}

Se suas células tiverem hieght diferente, devolva-o também:

override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    return super.tableView(tableView, heightForRowAtIndexPath: self.mode.indexPathForIndexPath(indexPath))
}
Petr Syrov
fonte
0

Além da solução @Saleh Masum:

Se você receber erros de layout automático , poderá remover as restrições dotableViewCell.contentView

Swift 3:

override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    let tableViewCell = super.tableView(tableView, cellForRowAt: indexPath)

    if tableViewCell.isHidden == true
    {
        tableViewCell.contentView.removeConstraints(tableViewCell.contentView.constraints)
        return 0
    }
    else{
        return super.tableView(tableView, heightForRowAt: indexPath)
    }

}

Esta solução depende do fluxo do seu aplicativo . Se você deseja mostrar / ocultar a célula na mesma instância do controlador de exibição, talvez essa não seja a melhor opção, pois remove as restrições .

Luca
fonte
0

Eu tenho uma maneira melhor de ocultar células estáticas e até seções dinamicamente sem hacks.

Definir a altura da linha como 0 pode ocultar uma linha, mas isso não funciona se você deseja ocultar uma seção inteira que conterá alguns espaços, mesmo que você oculte todas as linhas.

Minha abordagem é criar uma matriz de seções de células estáticas. Em seguida, o conteúdo da visualização da tabela será direcionado pela matriz de seção.

Aqui está um código de exemplo:

var tableSections = [[UITableViewCell]]()

private func configTableSections() {
    // seciton A
    tableSections.append([self.cell1InSectionA, self.cell2InSectionA])

    // section B
    if shouldShowSectionB {
        tableSections.append([self.cell1InSectionB, self.cell2InSectionB])
    }

    // section C
    if shouldShowCell1InSectionC {
        tableSections.append([self.cell1InSectionC, self.cell2InSectionC, self.cell3InSectionC])
    } else {
        tableSections.append([self.cell2InSectionC, self.cell3InSectionC])
    }
}

func numberOfSections(in tableView: UITableView) -> Int {
    return tableSections.count
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return tableSections[section].count
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    return tableSections[indexPath.section][indexPath.row]
}

Dessa forma, você pode reunir todo o seu código de configuração sem precisar escrever o código desagradável para calcular o número de linhas e seções. E, claro, não há 0mais alturas.

Este código também é muito fácil de manter. Por exemplo, se você deseja adicionar / remover mais células ou seções.

Da mesma forma, você pode criar uma matriz de título de cabeçalho de seção e matriz de título de rodapé de seção para configurar seus títulos de seção dinamicamente.

Simon Wang
fonte