Come implementare le intestazioni e i piè di pagina della sezione Vista tabella personalizzata con Storyboard


176

Senza usare uno storyboard, potremmo semplicemente trascinare un oggetto UIViewsulla tela, disporlo e quindi impostarlo nei metodi tableView:viewForHeaderInSectiono tableView:viewForFooterInSectiondelegare.

Come possiamo ottenere questo risultato con uno StoryBoard in cui non possiamo trascinare un UIView sulla tela

Risposte:


91

So che questa domanda era per iOS 5, ma a beneficio dei futuri lettori, nota che iOS 6 efficace ora possiamo usare al dequeueReusableHeaderFooterViewWithIdentifierposto di dequeueReusableCellWithIdentifier.

Quindi viewDidLoad, chiama registerNib:forHeaderFooterViewReuseIdentifier:o registerClass:forHeaderFooterViewReuseIdentifier:. Quindi viewForHeaderInSection, chiama tableView:dequeueReusableHeaderFooterViewWithIdentifier:. Non usi un prototipo di cella con questa API (è una vista basata su NIB o una vista creata a livello di programmazione), ma questa è la nuova API per intestazioni e piè di pagina dequeued.


13
sospiro Un peccato che queste due risposte chiare sull'uso di un NIB al posto di una cella proto siano attualmente sotto i 5 voti e la risposta "
hackeralo

5
La differenza è che con l'hack di Tieme puoi realizzare il tuo design nello stesso storyboard e non usare un Xib separato
CedricSoubrie,

Puoi in qualche modo evitare di utilizzare un file NIB separato e utilizzare invece lo storyboard?
Foriger,

@Foriger - Non a questo punto. È una strana omissione nelle viste della tabella dello storyboard, ma è quello che è. Usa quel kludgy hack con le celle prototipo se vuoi, ma personalmente, ho appena sopportato il fastidio dei NIB per le visualizzazioni di intestazione / piè di pagina.
Rob,

2
Dire semplicemente che è "vecchio" non è abbastanza forte, IMHO. La risposta accettata è un modo kludgy per aggirare il fatto che non è possibile avere celle prototipo per le visualizzazioni di intestazione e piè di pagina. Ma penso che sia semplicemente sbagliato, perché presume che il dequeeding di intestazioni e piè di pagina funzioni allo stesso modo del dequeuing di celle (che la presenza di una nuova API per intestazioni / piè di pagina suggerisce che potrebbe non essere il caso). La risposta accettata è una soluzione creativa comprensibile in iOS 5, ma, in iOS 6 e versioni successive, è meglio usare l'API esplicitamente progettata per il riutilizzo di intestazione / piè di pagina.
Rob,

384

Basta usare una cella prototipo come intestazione di sezione e / o piè di pagina.

  • aggiungi una cella in più e inserisci gli elementi desiderati in essa.
  • impostare l'identificatore su qualcosa di specifico (nel mio caso SectionHeader)
  • implementare il tableView:viewForHeaderInSection:metodo o il tableView:viewForFooterInSection:metodo
  • usare [tableView dequeueReusableCellWithIdentifier:]per ottenere l'intestazione
  • implementare il tableView:heightForHeaderInSection:metodo

(vedi screenshot)

-(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;
}  

Modifica: come modificare il titolo dell'intestazione (domanda commentata):

  1. Aggiungi un'etichetta alla cella dell'intestazione
  2. imposta il tag dell'etichetta su un numero specifico (es. 123)
  3. Nel tuo tableView:viewForHeaderInSection:metodo ottieni l'etichetta chiamando:
    UILabel *label = (UILabel *)[headerView viewWithTag:123]; 
  1. Ora puoi usare l'etichetta per impostare un nuovo titolo:
    [label setText:@"New Title"];

7
In ritardo alla festa, ma per disabilitare i clic sulla vista dell'intestazione della sezione, è sufficiente restituire cell.contentView nel viewForHeaderInSection metodo (non è necessario aggiungere UIVview personalizzate).
paulvs

3
@PaulVon Ho provato a farlo nel mio ultimo progetto, ma se lo fai prova a premere a lungo su una delle tue intestazioni e andrà in crash
Hons,

13
In iOS 6.0, dequeueReusableHeaderFooterViewWithIdentifierviene introdotto ed è ora preferito a questa risposta. Ma usarlo correttamente ora richiede più passaggi. Ho guida @ samwize.com/2015/11/06/… .
Samwize,

3
Non creare la sezione SectionHeaderViews utilizzando UITableViewCells, creerà un comportamento imprevisto. Utilizzare invece una vista UIV.
Apprezzo il

4
Non utilizzare mai UITableViewCellcome vista di intestazione. Risulterà molto difficile eseguire il debug dei difetti visivi: l'intestazione a volte scompare a causa del modo in cui le celle vengono rimosse, e per ore cercherete perché fino a quando non realizzerete UITableViewCellche non appartiene all'intestazione UITableView.
raven_raven,

53

In iOS 6.0 e versioni successive, le cose sono cambiate con la nuova dequeueReusableHeaderFooterViewWithIdentifierAPI.

Ho scritto una guida (testata su iOS 9), che può essere riassunta come tale:

  1. sottoclasse UITableViewHeaderFooterView
  2. Crea pennino con la vista della sottoclasse e aggiungi 1 vista contenitore che contiene tutte le altre viste nell'intestazione / piè di pagina
  3. Registrare il pennino in viewDidLoad
  4. Implementare viewForHeaderInSectione utilizzare dequeueReusableHeaderFooterViewWithIdentifierper ripristinare l'intestazione / piè di pagina

2
Grazie per questa risposta che NON è un trucco e il modo giusto di fare le cose. Inoltre, grazie per avermi fatto conoscere UITableViewHeaderFooterVIew. A proposito, la guida è semplicemente eccellente! :)
Roboris,

Benvenuto. L'implementazione dell'intestazione / piè di pagina per la vista di tabelle o raccolte non è ben documentata da Apple. Proprio ieri sono rimasto bloccato nell'intestazione getter per mostrarlo durante la progettazione tramite storyboad .
Samwize

1
Questa dovrebbe essere la risposta più votata di sicuro!
Ben Williams,

Potresti spiegare come farlo tramite storyboard, anziché xib? Nel fare ciò, mi acciogo con successo, ma i miei UILabel sono nulli.
bunkerdive,

22

L'ho fatto funzionare in iOS7 usando una cellula prototipo nello storyboard. Ho un pulsante nella vista intestazione della sezione personalizzata che attiva un seguito impostato nello storyboard.

Inizia con la soluzione di Tieme

Come sottolinea pedro.m, il problema è che toccando l'intestazione della sezione si seleziona la prima cella della sezione.

Come sottolinea Paul Von, questo problema viene risolto restituendo contentView della cella anziché l'intera cella.

Tuttavia, come sottolineato da Hons, una lunga pressione sull'intestazione della sezione provoca l'arresto anomalo dell'app.

La soluzione è rimuovere qualsiasi gestoRecognizer da 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 non stai usando i gesti nelle viste dell'intestazione della sezione, questo piccolo trucco sembra averlo fatto.


2
Hai ragione. Ho appena testato questo e il suo funzionamento, quindi ho aggiornato il mio post ( hons82.blogspot.it/2014/05/uitableviewheader-done-right.html ) contenente tutte le possibili soluzioni che ho trovato durante la mia ricerca. Grazie mille per questa correzione
Hons

Buona risposta ... Grazie
amico

13

Se usi gli storyboard, puoi utilizzare una cella prototipo nella vista tabella per impaginare la vista intestazione. Imposta un ID univoco e viewForHeaderInSection puoi dequeue la cella con quell'ID e trasmetterlo a una vista UIV.


12

Se hai bisogno di un'implementazione rapida di questo segui le indicazioni sulla risposta accettata e poi in UITableViewController implementa i seguenti metodi:

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
}

2
Per qualche motivo non ha funzionato per me fino a quando non ho ignorato heightForHeaderInSection.
Entalpi,

Questo è un codice piuttosto vecchio. dovresti pubblicare un'altra risposta!
JZ.

Ero bloccato perché non sapevo che avrei dovuto scavalcare heightForHeaderInSection. Grazie @Entalpi
guijob l'

1
@JZ. Nel tuo esempio di Swift 3, esegui la deque con i seguenti self.tableView.dequeueReusableHeaderFooterView e Swift 2: self.tableView.dequeueReusableCellWithIdentifier c'è differenza?
Vrutin Rathod,

1
Questo mi ha salvato il culo! l'aggiunta di un'altezza a headerCell lo rende visibile.
CRey

9

La soluzione che ho trovato è sostanzialmente la stessa soluzione utilizzata prima dell'introduzione degli storyboard.

Creare un nuovo file di classe di interfaccia vuoto. Trascina un UIView sulla tela, layout come desiderato.

Caricare manualmente il pennino, assegnare alla sezione intestazione / piè di pagina appropriata nei metodi delegati viewForHeaderInSection o viewForFooterInSection.

Speravo che Apple semplificasse questo scenario con gli storyboard e continuasse a cercare una soluzione migliore o più semplice. Ad esempio, le intestazioni e i piè di pagina personalizzati della tabella sono semplici da aggiungere.


Beh, Apple l'ha fatto se usi la cella dello storyboard come metodo di intestazione :) stackoverflow.com/questions/9219234/#11396643
Tieme

2
Tranne che funziona solo per celle prototipo, non per celle statiche.
Jean-Denis Muys,

1
Una soluzione davvero semplice è usare Pixate ; puoi fare un bel po 'di personalizzazione senza ottenere un riferimento all'intestazione. Tutto quello che devi implementare è tableView:titleForHeaderInSection, che è una linea.
John Starr Dewar,

5
Questa è la vera soluzione ... purtroppo non è in cima, quindi mi ci è voluto un po 'per scoprirlo. Ho riassunto i problemi che ho avuto hons82.blogspot.it/2014/05//ableableviewheader-done-right.html
Hons

È più facile di così, basta trascinare e rilasciare.
Ricardo,

5

Quando restituisci il contenuto della cella Visualizza avrai 2 problemi:

  1. incidente legato ai gesti
  2. non riutilizzare contentView (ogni volta che si viewForHeaderInSectionchiama, si crea una nuova cella)

Soluzione:

Classe wrapper per l'intestazione della tabella \ piè di pagina. È solo un contenitore, ereditato da UITableViewHeaderFooterView, che contiene la cellula all'interno

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

Registrare la classe in UITableView (ad esempio in viewDidLoad)

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

Nel tuo 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;
}

2

In passato ho usato pigramente le visualizzazioni di intestazione / piè di pagina:

  • Aggiungi un controller di visualizzazione a mano libera per l'intestazione / piè di pagina della sezione nello storyboard
  • Gestisci tutte le cose per l'intestazione nel controller di visualizzazione
  • Nel controller della vista tabella fornire un array mutabile di controller di vista per le intestazioni / piè di pagina delle sezioni ripopolate con [NSNull null]
  • In viewForHeaderInSection / viewForFooterInSection se il controller della vista non esiste ancora, crealo con gli storyboard istanziatiViewControllerWithIdentifier, ricordalo nell'array e restituisci la vista dei controller della vista

2

Sono stato nei guai in uno scenario in cui Header non è mai stato riutilizzato nemmeno facendo tutti i passi giusti.

Quindi, come nota di suggerimento per tutti coloro che vogliono raggiungere la situazione di mostra sezioni vuote (0 righe), avvisa che:

dequeueReusableHeaderFooterViewWithIdentifier non riutilizzerà l'intestazione finché non si restituisce almeno una riga

Spero che sia d'aiuto


1

Per dare seguito al suggerimento di Damon , ecco come ho reso l'intestazione selezionabile proprio come una riga normale con un indicatore di divulgazione.

Ho aggiunto un pulsante subclassato da UIButton (nome della sottoclasse "ButtonWithArgument") alla cella del prototipo dell'intestazione e cancellato il testo del titolo (il testo in grassetto "Title" è un altro UILabel nella cella del prototipo)

Pulsante In Interface Builder

quindi imposta il pulsante sull'intera vista dell'intestazione e aggiunge un indicatore di divulgazione con il trucco di 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

1

Dovresti usare la soluzione di Tieme come base, ma dimentica gli viewWithTag:altri approcci di pesce, invece prova a ricaricare l'intestazione (ricaricando quella sezione).

Quindi, dopo aver aperto la visualizzazione della tua intestazione di cella personalizzata con tutte le AutoLayoutcose fantasiose , basta rimuoverlo e restituire il contenuto Visualizza dopo la configurazione, come:

-(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;
}  

1

Che dire di una soluzione in cui l'intestazione si basa su un array di visualizzazione:

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

Il prossimo nel delegato:

extension myViewController: UITableViewDelegate {
    func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        return(header[section])
    }
}

1
  1. Aggiungi cella StoryBoarde impostareuseidentified

    sb

  2. Codice

    class TP_TaskViewTableViewSectionHeader: UITableViewCell{
    }
    

    e

    collegamento

  3. Uso:

    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let header = tableView.dequeueReusableCell(withIdentifier: "header", for: IndexPath.init(row: 0, section: section))
        return header
    }
    

2
Questo non è il modo corretto di aggiungere un Header
Manish Malviya il

2
allora qual è la strada?
Pramod Shukla,

1

Simile alla risposta laszlo ma è possibile riutilizzare la stessa cella prototipo sia per le celle della tabella che per la cella dell'intestazione della sezione. Aggiungi le prime due funzioni seguenti alla sottoclasse di 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
}

0

Ecco la risposta di @Vitaliy Gozhenko , in Swift.
Per riassumere creerai un UITableViewHeaderFooterView che contiene un UITableViewCell. Questo UITableViewCell sarà "dequeuabile" e puoi progettarlo nello storyboard.

  1. Creare una 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. Collega la tabella con questa classe nella funzione viewDidLoad:

    self.tableView.registerClass(CustomHeaderFooterView.self, forHeaderFooterViewReuseIdentifier: "SECTION_ID")
    
  3. Quando chiedi, per un'intestazione di sezione, dequeue un CustomHeaderFooterView e inserisci una cella in esso

    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;
    }
    
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.