Como implementar cabeçalhos e rodapés de seção de exibição de tabela personalizada com storyboard

176

Sem usar um storyboard, poderíamos simplesmente arrastar um UIViewpara a tela, colocá-lo em um layout e depois defini-lo nos métodos tableView:viewForHeaderInSectionou tableView:viewForFooterInSectiondelegar.

Como podemos fazer isso com um StoryBoard em que não podemos arrastar um UIView para a tela

Seamus
fonte

Respostas:

90

Eu sei que essa pergunta foi para o iOS 5, mas para o benefício de futuros leitores, observe que o iOS 6 eficaz agora podemos usar em dequeueReusableHeaderFooterViewWithIdentifiervez de dequeueReusableCellWithIdentifier.

Então viewDidLoad, ligue para registerNib:forHeaderFooterViewReuseIdentifier:ou registerClass:forHeaderFooterViewReuseIdentifier:. Então viewForHeaderInSection, ligue tableView:dequeueReusableHeaderFooterViewWithIdentifier:. Você não usa um protótipo de célula com essa API (é uma visão baseada em NIB ou uma visão criada programaticamente), mas essa é a nova API para cabeçalhos e rodapés desenfileirados.

Roubar
fonte
13
suspirar Uma vergonha que essas duas respostas limpas sobre como usar um NIB vez de uma célula proto estão atualmente em 5 votos, eo "cortá-lo com células proto" resposta é acima de 200.
Benjohn
5
A diferença é que, com o corte de Tieme você pode fazer seu projeto no mesmo storyboard e não usar um Xib separado
CedricSoubrie
Você pode de alguma forma evitar o uso de arquivo NIB separado e usar o storyboard?
Foriger
@ Foriger - Não neste momento. É uma omissão estranha nas visualizações da tabela de storyboard, mas é o que é. Use esse hack kludgy com células de protótipo, se quiser, mas, pessoalmente, apenas atendo ao aborrecimento dos NIBs pelas visualizações de cabeçalho / rodapé.
Rob
2
Apenas dizer que é "velho" não é forte o suficiente, IMHO. A resposta aceita é uma maneira arrogante de contornar o fato de que você não pode ter células protótipo para visualizações de cabeçalho e rodapé. Mas acho que está simplesmente errado, porque pressupõe que o desenfileiramento de cabeçalhos e rodapés funciona da mesma maneira que desenfileirar células (o que sugere a presença de uma API mais recente para cabeçalhos / rodapés). A resposta aceita é uma solução criativa que era compreensível no iOS 5, mas, no iOS 6 e posterior, é melhor usar a API explicitamente projetada para reutilização de cabeçalho / rodapé.
Rob
384

Basta usar uma célula protótipo como cabeçalho e / ou rodapé da seção.

  • adicione uma célula extra e coloque os elementos desejados nela.
  • defina o identificador para algo específico (no meu caso, SectionHeader)
  • implementar o tableView:viewForHeaderInSection:método ou o tableView:viewForFooterInSection:método
  • use [tableView dequeueReusableCellWithIdentifier:]para obter o cabeçalho
  • implementar o tableView:heightForHeaderInSection:método

(veja a captura de tela)

-(UIView *) tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
    static NSString *CellIdentifier = @"SectionHeader"; 
    UITableViewCell *headerView = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (headerView == nil){
        [NSException raise:@"headerView == nil.." format:@"No cells with matching CellIdentifier loaded from your storyboard"];
    }
    return headerView;
}  

Editar: Como alterar o título do cabeçalho (pergunta comentada):

  1. Adicione um rótulo à célula do cabeçalho
  2. defina a etiqueta da etiqueta para um número específico (por exemplo, 123)
  3. No seu tableView:viewForHeaderInSection:método, obtenha o rótulo chamando:
    UILabel *label = (UILabel *)[headerView viewWithTag:123]; 
  1. Agora você pode usar o rótulo para definir um novo título:
    [label setText:@"New Title"];
Me amarre
fonte
7
Tarde para a parte, mas para desativar os cliques na visualização do cabeçalho da seção, basta retornar cell.contentView no viewForHeaderInSection método (não é necessário adicionar UIViews personalizados).
paulvs 29/05
3
@PaulVon Tentei fazer isso no meu projeto mais recente, mas se você fizer isso, tente pressionar longamente um de seus cabeçalhos e ele irá travar
Hons:
13
No iOS 6.0, dequeueReusableHeaderFooterViewWithIdentifieré apresentado e agora é preferido em relação a esta resposta. Mas usar isso corretamente agora leva mais etapas. Tenho guide @ samwize.com/2015/11/06/… .
Samwize
3
Não crie seu sectionHeaderViews usando UITableViewCells, ele criará um comportamento inesperado. Use um UIView.
AppreciateIt
4
Por favor, nunca use UITableViewCellcomo uma visualização de cabeçalho. Você terá muita dificuldade em depurar falhas visuais - o cabeçalho às vezes desaparecerá devido à maneira como as células são desenfileiradas e você estará procurando por horas por que isso ocorre até que você perceba UITableViewCellque não pertence ao UITableViewcabeçalho.
Raven_raven 28/09
53

No iOS 6.0 e superior, as coisas mudaram com a nova dequeueReusableHeaderFooterViewWithIdentifierAPI.

Escrevi um guia (testado no iOS 9), que pode ser resumido da seguinte forma:

  1. Subclasse UITableViewHeaderFooterView
  2. Crie Nib com a visualização de subclasse e adicione 1 visualização de contêiner que contém todas as outras visualizações no cabeçalho / rodapé
  3. Registre a ponta em viewDidLoad
  4. Implementar viewForHeaderInSectione usar dequeueReusableHeaderFooterViewWithIdentifierpara recuperar o cabeçalho / rodapé
samwize
fonte
2
Obrigado por esta resposta, que NÃO é um hack, e a maneira correta de fazer as coisas. Além disso, obrigado por me apresentar o UITableViewHeaderFooterVIew. Aliás, o guia é simplesmente excelente! :)
Roboris
Bem-vinda. A implementação de cabeçalho / rodapé para exibição de tabela ou coleção não é muito bem documentada pela Apple. Ontem mesmo eu estava preso no cabeçalho getter para mostrar ao projetar via storyboad .
Samwize
1
Essa deve ser a resposta mais bem avaliada, com certeza!
Ben Williams
Você poderia explicar como fazer isso através do storyboard, em vez do xib? Ao fazer isso, retiro a fila com êxito, mas meus UILabels são nulos.
bunkerdive
22

Eu consegui trabalhar no iOS7 usando uma célula protótipo no storyboard. Eu tenho um botão na visualização de cabeçalho de seção personalizada que aciona uma sequência configurada no storyboard.

Comece com a solução do Tieme

Como aponta pedro.m, o problema é que tocar no cabeçalho da seção faz com que a primeira célula da seção seja selecionada.

Como Paul Von aponta, isso é corrigido retornando o contentView da célula em vez da célula inteira.

No entanto, como Hons aponta, uma pressão longa no cabeçalho da seção travará o aplicativo.

A solução é remover qualquer gestor de reconhecimento do contentView.

-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
     static NSString *CellIdentifier = @"SectionHeader";
     UITableViewCell *sectionHeaderView = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

     while (sectionHeaderView.contentView.gestureRecognizers.count) {
         [sectionHeaderView.contentView removeGestureRecognizer:[sectionHeaderView.contentView.gestureRecognizers objectAtIndex:0]];
     }

     return sectionHeaderView.contentView; }

Se você não estiver usando gestos nas visualizações do cabeçalho da seção, esse pequeno truque parece ser feito.

Damon
fonte
2
Você está certo. Acabei de testar isso e seu funcionamento, então atualizei minha postagem ( hons82.blogspot.it/2014/05/uitableviewheader-done-right.html ) contendo todas as soluções possíveis que encontrei durante minha pesquisa. Um monte Thx para essa correção
Hons
Boa resposta ... Obrigado, homem
Jogendra.Com
13

Se você usa storyboards, pode usar uma célula protótipo na tableview para fazer o layout da visualização do cabeçalho. Defina um ID exclusivo e viewForHeaderInSection. Você pode desenfileirar a célula com esse ID e convertê-lo em um UIView.

barksten
fonte
12

Se você precisar de uma implementação rápida, siga as instruções na resposta aceita e, em seguida, no UITableViewController, implemente os seguintes métodos:

override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
    return tableView.dequeueReusableCell(withIdentifier: "CustomHeader")
}

override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
    return 75
}
JZ.
fonte
2
Por alguma razão, não funcionou para mim até que eu tenha ultrapassado o heightForHeaderInSection também.
Entalpi
Este é um código bastante antigo. você deve postar outra resposta!
JZ.
Eu estava preso porque não sabia que teria que substituir heightForHeaderInSection. Obrigado @Entalpi
guijob
1
@JZ. No seu exemplo do Swift 3, você faz o processo com os seguintes self.tableView.dequeueReusableHeaderFooterView e Swift 2: self.tableView.dequeueReusableCellWithIdentifier há diferença?
Vrutin Rathod
1
Isso salvou minha bunda! adicionar uma altura ao headerCell torna-o visível.
CRey
9

A solução que eu criei é basicamente a mesma solução usada antes da introdução dos storyboards.

Crie um novo arquivo de classe de interface vazio. Arraste um UIView para a tela, layout conforme desejado.

Carregue a ponta manualmente, atribua à seção apropriada de cabeçalho / rodapé nos métodos delegados viewForHeaderInSection ou viewForFooterInSection.

Eu esperava que a Apple simplificasse esse cenário com storyboards e continuasse procurando uma solução melhor ou mais simples. Por exemplo, cabeçalhos e rodapés de tabelas personalizados são fáceis de adicionar.

Seamus
fonte
Bem a Apple fez se você usar o celular storyboard como método de cabeçalho :) stackoverflow.com/questions/9219234/#11396643
Tieme
2
Exceto que funciona apenas para células protótipo, não para células estáticas.
Jean-Denis Muys
1
Uma solução realmente fácil é usar o Pixate ; você pode fazer muitas personalizações sem obter uma referência ao cabeçalho. Tudo o que você precisa implementar é o tableView:titleForHeaderInSectionone-liner.
John Starr Dewar
5
Essa é a solução real ... infelizmente não está no topo, então demorei um pouco para descobrir. Resumi os problemas que tive hons82.blogspot.it/2014/05/uitableviewheader-done-right.html
Hons:
É mais fácil do que isso, basta arrastar e soltar.
Ricardo
5

Ao retornar o contentView da célula, você terá 2 problemas:

  1. acidente relacionado a gestos
  2. você não reutiliza o contentView (sempre que está de viewForHeaderInSectionplantão, está criando uma nova célula)

Solução:

Classe de wrapper para cabeçalho da tabela \ rodapé. É apenas um recipiente, herdado de UITableViewHeaderFooterView, que mantém a célula dentro

https://github.com/Magnat12/MGTableViewHeaderWrapperView.git

Registre a classe no seu UITableView (por exemplo, em viewDidLoad)

- (void)viewDidLoad {
    [super viewDidLoad];
    [self.tableView registerClass:[MGTableViewHeaderWrapperView class] forHeaderFooterViewReuseIdentifier:@"ProfileEditSectionHeader"];
}

No seu UITableViewDelegate:

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
    MGTableViewHeaderWrapperView *view = [tableView dequeueReusableHeaderFooterViewWithIdentifier:@"ProfileEditSectionHeader"];

    // init your custom cell
    ProfileEditSectionTitleTableCell *cell = (ProfileEditSectionTitleTableCell * ) view.cell;
    if (!cell) {
        cell = [tableView dequeueReusableCellWithIdentifier:@"ProfileEditSectionTitleTableCell"];
        view.cell = cell;
    }

    // Do something with your cell

    return view;
}
Vitaliy Gozhenko
fonte
2

Eu costumava fazer o seguinte para criar vistas de cabeçalho / rodapé preguiçosamente:

  • Adicione um controlador de exibição de forma livre para o cabeçalho / rodapé da seção ao storyboard
  • Manipular todas as coisas para o cabeçalho no controlador de exibição
  • Na tabela, o controlador de visualização fornece uma matriz mutável de controladores de visualização para os cabeçalhos / rodapés da seção preenchidos novamente com [NSNull null]
  • Em viewForHeaderInSection / viewForFooterInSection se o controlador de exibição ainda não existir, crie-o com storyboards instantiateViewControllerWithIdentifier, lembre-se dele na matriz e retorne a exibição de controladores de exibição
Vipera Berus
fonte
2

Eu estive com problemas em um cenário em que o Header nunca foi reutilizado, mesmo executando todas as etapas adequadas.

Portanto, como uma dica para todos que desejam alcançar a situação de mostrar seções vazias (0 linhas), lembre-se de que:

dequeueReusableHeaderFooterViewWithIdentifier não reutilizará o cabeçalho até você retornar pelo menos uma linha

Espero que ajude

Juan Pedro Lozano
fonte
1

Para acompanhar a sugestão de Damon , aqui está como tornei o cabeçalho selecionável como uma linha normal com um indicador de divulgação.

Adicionei um botão subclassificado de UIButton (nome da subclasse "ButtonWithArgument") à célula protótipo do cabeçalho e excluí o texto do título (o texto em negrito "Título" é outro UILabel na célula protótipo)

Botão No Interface Builder

em seguida, defina o botão para a visualização inteira do cabeçalho e adicione um indicador de divulgação com o truque de Avario

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
    static NSString *CellIdentifier = @"PersonGroupHeader";
    UITableViewCell *headerView = (UITableViewCell *) [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if(headerView == nil)
    {
        [NSException raise:@"headerView == nil, PersonGroupTableViewController" format:[NSString stringWithFormat:@"Storyboard does not have prototype cell with identifier %@",CellIdentifier]];
    }

    //  https://stackoverflow.com/a/24044628/3075839
    while(headerView.contentView.gestureRecognizers.count)
    {
        [headerView.contentView removeGestureRecognizer:[headerView.contentView.gestureRecognizers objectAtIndex:0]];
    }


    ButtonWithArgument *button = (ButtonWithArgument *)[headerView viewWithTag:4];
    button.frame = headerView.bounds; // set tap area to entire header view
    button.argument = [[NSNumber alloc] initWithInteger:section]; // from ButtonWithArguments subclass
    [button addTarget:self action:@selector(headerViewTap:) forControlEvents:UIControlEventTouchUpInside];

    // https://stackoverflow.com/a/20821178/3075839
    UITableViewCell *disclosure = [[UITableViewCell alloc] init];
    disclosure.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    disclosure.userInteractionEnabled = NO;
    disclosure.frame = CGRectMake(button.bounds.origin.x + button.bounds.size.width - 20 - 5, // disclosure 20 px wide, right margin 5 px
          (button.bounds.size.height - 20) / 2,
          20,
          20);
    [button addSubview:disclosure];

    // configure header title text

    return headerView.contentView;
}

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
    return 35.0f;
}

-(void) headerViewTap:(UIGestureRecognizer *)gestureRecognizer;
{
    NSLog(@"header tap");
    NSInteger section = ((NSNumber *)sender.argument).integerValue;
    // do something here
}

ButtonWithArgument.h

#import <UIKit/UIKit.h>

@interface ButtonWithArgument : UIButton
@property (nonatomic, strong) NSObject *argument;
@end

ButtonWithArgument.m

#import "ButtonWithArgument.h"
@implementation ButtonWithArgument
@end
MarkF
fonte
1

Você deve usar a solução do Tieme como base, mas esqueça oviewWithTag: abordagens suspeitas e outras, em vez disso, tente recarregar o cabeçalho (recarregando essa seção).

Então, depois de exibir sua visualização de cabeçalho de célula personalizada com todas as AutoLayoutcoisas sofisticadas , basta desenfileirá-la e retornar o contentView após a configuração, como:

-(UIView *) tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
 static NSString *CellIdentifier = @"SectionHeader"; 

    SettingsTableViewCell *sectionHeaderCell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    sectionHeaderCell.myPrettyLabel.text = @"Greetings";
    sectionHeaderCell.contentView.backgroundColor = [UIColor whiteColor]; // don't leave this transparent

    return sectionHeaderCell.contentView;
}  
Laszlo
fonte
1

Que tal uma solução em que o cabeçalho é baseado em uma matriz de exibição:

class myViewController: UIViewController {
    var header: [UILabel] = myStringArray.map { (thisTitle: String) -> UILabel in
        let headerView = UILabel()
            headerView.text = thisTitle
    return(headerView)
}

Em seguida no delegado:

extension myViewController: UITableViewDelegate {
    func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        return(header[section])
    }
}
CyrIng
fonte
1
  1. Adicione célula StoryBoarde definareuseidentified

    sb

  2. Código

    class TP_TaskViewTableViewSectionHeader: UITableViewCell{
    }
    

    e

    ligação

  3. Usar:

    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let header = tableView.dequeueReusableCell(withIdentifier: "header", for: IndexPath.init(row: 0, section: section))
        return header
    }
    
leo.tan
fonte
2
Esta não é a maneira correta de adicionar um cabeçalho
Manish Malviya
2
então qual é o caminho?
Pramod Shukla
1

Semelhante à resposta laszlo, mas você pode reutilizar a mesma célula protótipo para as células da tabela e a célula do cabeçalho da seção. Adicione as duas primeiras funções abaixo à sua subclasse UIViewController

override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let cell = tableView.dequeueReusableCell(withIdentifier: "DataCell") as! DataCell
    cell.data1Label.text = "DATA KEY"
    cell.data2Label.text = "DATA VALUE"
    return cell
}

override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
    return 75
}

// Example of regular data cell dataDelegate to round out the example
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "DataCell", for: indexPath) as! PlayerCell

    cell.data1Label.text = "\(dataList[indexPath.row].key)"
    cell.data2Label.text = "\(dataList[indexPath.row].value)"
    return cell
}
Richard Legault
fonte
0

Aqui está a resposta de @Vitaliy Gozhenko , em Swift.
Para resumir, você criará um UITableViewHeaderFooterView que contém um UITableViewCell. Este UITableViewCell será "dequeuable" e você poderá projetá-lo em seu storyboard.

  1. Crie uma classe UITableViewHeaderFooterView

    class CustomHeaderFooterView: UITableViewHeaderFooterView {
    var cell : UITableViewCell? {
        willSet {
            cell?.removeFromSuperview()
        }
        didSet {
            if let cell = cell {
                cell.frame = self.bounds
                cell.autoresizingMask = [UIViewAutoresizing.FlexibleHeight, UIViewAutoresizing.FlexibleWidth]
                self.contentView.backgroundColor = UIColor .clearColor()
                self.contentView .addSubview(cell)
            }
        }
    }
    
  2. Conecte seu tableview com esta classe na sua função viewDidLoad:

    self.tableView.registerClass(CustomHeaderFooterView.self, forHeaderFooterViewReuseIdentifier: "SECTION_ID")
    
  3. Ao solicitar um cabeçalho de seção, desenfileire um CustomHeaderFooterView e insira uma célula nele

    func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let view = self.tableView.dequeueReusableHeaderFooterViewWithIdentifier("SECTION_ID") as! CustomHeaderFooterView
        if view.cell == nil {
            let cell = self.tableView.dequeueReusableCellWithIdentifier("Cell")
            view.cell = cell;
        }
    
        // Fill the cell with data here
    
        return view;
    }
    
CedricSoubrie
fonte