Implementieren von Kopf- und Fußzeilen für benutzerdefinierte Tabellenansichtsabschnitte mit Storyboard

176

Ohne ein Storyboard könnten wir einfach ein UIViewauf die Leinwand ziehen, es auslegen und dann in den Methoden tableView:viewForHeaderInSectionoder tableView:viewForFooterInSectiondelegieren festlegen .

Wie erreichen wir dies mit einem StoryBoard, bei dem wir kein UIView auf die Leinwand ziehen können?

Seamus
quelle

Antworten:

90

Ich weiß, dass diese Frage für iOS 5 war, aber zum Nutzen zukünftiger Leser beachten Sie, dass wir jetzt effektives iOS 6 dequeueReusableHeaderFooterViewWithIdentifieranstelle von verwenden können dequeueReusableCellWithIdentifier.

Also viewDidLoad, rufen Sie entweder registerNib:forHeaderFooterViewReuseIdentifier:oder registerClass:forHeaderFooterViewReuseIdentifier:. Dann viewForHeaderInSectionrufen Sie an tableView:dequeueReusableHeaderFooterViewWithIdentifier:. Sie verwenden mit dieser API keinen Zellenprototyp (es handelt sich entweder um eine NIB-basierte Ansicht oder eine programmgesteuert erstellte Ansicht), dies ist jedoch die neue API für Kopf- und Fußzeilen in der Warteschlange.

rauben
quelle
13
Seufzer Schade, dass diese beiden sauberen Antworten zur Verwendung einer NIB anstelle einer Protozelle derzeit unter 5 Stimmen liegen und die Antwort "Mit Protozellen hacken" über 200 liegt.
Benjohn
5
Der Unterschied ist, dass Sie mit dem Hack von Tieme Ihr Design im selben Storyboard
erstellen können
Können Sie es irgendwie vermeiden, eine separate NIB-Datei zu verwenden und stattdessen ein Storyboard zu verwenden?
Foriger
@ Foriger - Noch nicht. Es ist eine merkwürdige Auslassung in Storyboard-Tabellenansichten, aber es ist, was es ist. Verwenden Sie diesen klobigen Hack mit Prototypzellen, wenn Sie möchten, aber ich persönlich habe mich nur mit dem Ärger der NIBs für die Kopf- / Fußzeilenansichten abgefunden.
Rob
2
Nur zu sagen, dass es "alt" ist, ist meiner Meinung nach nicht stark genug. Die akzeptierte Antwort ist ein umständlicher Weg, um die Tatsache zu umgehen, dass Sie keine Prototypzellen für Kopf- und Fußzeilenansichten haben können. Aber ich denke, es ist einfach falsch, weil davon ausgegangen wird, dass das Ausreihen von Kopf- und Fußzeilen genauso funktioniert wie das Ausreihen von Zellen (was das Vorhandensein einer neueren API für Kopf- / Fußzeilen möglicherweise nicht der Fall ist). Die akzeptierte Antwort ist eine kreative Problemumgehung, die in iOS 5 verständlich war. In iOS 6 und höher ist es jedoch am besten, die API zu verwenden, die explizit für die Wiederverwendung von Kopf- und Fußzeilen entwickelt wurde.
Rob
384

Verwenden Sie einfach eine Prototypzelle als Kopf- und / oder Fußzeile Ihres Abschnitts.

  • Fügen Sie eine zusätzliche Zelle hinzu und fügen Sie die gewünschten Elemente ein.
  • Setzen Sie den Bezeichner auf einen bestimmten Wert (in meinem Fall SectionHeader).
  • Implementieren Sie die tableView:viewForHeaderInSection:Methode oder die tableView:viewForFooterInSection:Methode
  • Verwenden Sie [tableView dequeueReusableCellWithIdentifier:], um den Header zu erhalten
  • Implementieren Sie die tableView:heightForHeaderInSection:Methode.

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

Bearbeiten: So ändern Sie den Header-Titel (kommentierte Frage):

  1. Fügen Sie der Kopfzelle eine Beschriftung hinzu
  2. Setzen Sie das Etikett auf eine bestimmte Nummer (z. B. 123).
  3. In Ihrer tableView:viewForHeaderInSection:Methode erhalten Sie das Label, indem Sie Folgendes aufrufen:
    UILabel *label = (UILabel *)[headerView viewWithTag:123]; 
  1. Jetzt können Sie mit der Bezeichnung einen neuen Titel festlegen:
    [label setText:@"New Title"];
Tieme
quelle
7
Spät zur Party, aber um Klicks auf die Abschnittsüberschriftenansicht zu deaktivieren, geben Sie einfach cell.contentView in der viewForHeaderInSection Methode zurück (es müssen keine benutzerdefinierten UIViews hinzugefügt werden).
Paulvs
3
@PaulVon Ich habe versucht, das in meinem neuesten Projekt zu tun, aber wenn Sie das tun, versuchen Sie einfach, auf einen Ihrer Header zu drücken, und es wird abstürzen
Hons
13
In iOS 6.0 dequeueReusableHeaderFooterViewWithIdentifierwird eingeführt und wird nun dieser Antwort vorgezogen. Aber wenn Sie das richtig verwenden, sind jetzt weitere Schritte erforderlich. Ich habe guide @ samwize.com/2015/11/06/… .
Samwize
3
Erstellen Sie Ihre sectionHeaderViews nicht mit UITableViewCells, da dies zu unerwartetem Verhalten führt. Verwenden Sie stattdessen eine UIView.
AppreciateIt
4
Bitte niemals UITableViewCellals Header-Ansicht verwenden. Es wird sehr schwierig sein, visuelle Störungen zu beheben - der Header verschwindet manchmal, weil Zellen aus der Warteschlange entfernt werden, und Sie werden stundenlang nach dem Grund suchen, warum dies so ist, bis Sie feststellen, UITableViewCelldass er nicht zum UITableViewHeader gehört.
Raven_raven
53

In iOS 6.0 und höher haben sich die Dinge mit der neuen dequeueReusableHeaderFooterViewWithIdentifierAPI geändert .

Ich habe einen Leitfaden geschrieben (getestet auf iOS 9), der als solcher zusammengefasst werden kann:

  1. Unterklasse UITableViewHeaderFooterView
  2. Erstellen Sie Nib mit der Unterklassenansicht und fügen Sie 1 Containeransicht hinzu, die alle anderen Ansichten in der Kopf- / Fußzeile enthält
  3. Registrieren Sie die Feder in viewDidLoad
  4. Implementieren viewForHeaderInSectionund verwenden Sie dequeueReusableHeaderFooterViewWithIdentifier, um die Kopf- / Fußzeile zurückzugewinnen
Samwize
quelle
2
Vielen Dank für diese Antwort, die KEIN Hack ist und die richtige Art, Dinge zu tun. Vielen Dank auch, dass Sie mich mit UITableViewHeaderFooterVIew bekannt gemacht haben. Übrigens ist der Führer einfach ausgezeichnet! :)
Roboris
Herzlich willkommen. Die Implementierung von Kopf- / Fußzeilen für die Tabellen- oder Sammlungsansicht ist von Apple nicht sehr gut dokumentiert. Erst gestern steckte ich im Getter-Header fest, um beim Entwerfen über Storyboad gezeigt zu werden .
Samwize
1
Dies sollte auf jeden Fall die am besten bewertete Antwort sein!
Ben Williams
Können Sie erklären, wie dies über das Storyboard anstelle von xib geschieht? Dabei stelle ich die Warteschlange erfolgreich ab, aber meine UILabels sind null.
Bunkerdive
22

Ich habe es in iOS7 mit einer Prototypzelle im Storyboard zum Laufen gebracht. In meiner benutzerdefinierten Abschnittsüberschriftenansicht befindet sich eine Schaltfläche, die einen im Storyboard eingerichteten Übergang auslöst.

Beginnen Sie mit der Lösung von Tieme

Wie pedro.m hervorhebt, besteht das Problem darin, dass durch Tippen auf die Abschnittsüberschrift die erste Zelle im Abschnitt ausgewählt wird.

Wie Paul Von betont, wird dies behoben, indem die Inhaltsansicht der Zelle anstelle der gesamten Zelle zurückgegeben wird.

Wie Hons jedoch betont, stürzt die App durch langes Drücken auf den Abschnittskopf ab.

Die Lösung besteht darin, alle gestureRecognizers aus contentView zu entfernen.

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

Wenn Sie in Ihren Abschnittsüberschriften keine Gesten verwenden, scheint dieser kleine Hack es zu schaffen.

Damon
quelle
2
Du hast recht. Ich habe das und seine Funktionsweise gerade getestet und meinen Beitrag ( hons82.blogspot.it/2014/05/uitableviewheader-done-right.html ) aktualisiert , der alle möglichen Lösungen enthält, die ich während meiner Recherche gefunden habe. Vielen Dank für dieses Update
Hons
Gute Antwort ... Danke Mann
Jogendra.Com
13

Wenn Sie Storyboards verwenden, können Sie eine Prototypzelle in der Tabellenansicht verwenden, um Ihre Kopfzeilenansicht zu gestalten. Legen Sie eine eindeutige ID und viewForHeaderInSection fest. Sie können die Zelle mit dieser ID aus der Warteschlange entfernen und in eine UIView umwandeln.

Barksten
quelle
12

Wenn Sie eine schnelle Implementierung benötigen, befolgen Sie die Anweisungen auf der akzeptierten Antwort und implementieren Sie dann in Ihrem UITableViewController die folgenden Methoden:

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.
quelle
2
Aus irgendeinem Grund hat es bei mir nicht funktioniert, bis ich heightForHeaderInSection ebenfalls überschrieben habe.
Entalpi
Das ist ziemlich alter Code. du solltest eine andere Antwort posten!
JZ.
Ich steckte fest, weil ich nicht wusste, dass ich heightForHeaderInSection überschreiben musste. Danke @Entalpi
Guijob
1
@JZ. In Ihrem Swift 3-Beispiel deque mit dem folgenden self.tableView.dequeueReusableHeaderFooterView und Swift 2: self.tableView.dequeueReusableCellWithIdentifier Gibt es einen Unterschied?
Vrutin Rathod
1
Das hat meinen Hintern gerettet! Durch Hinzufügen einer Höhe zu headerCell wird diese sichtbar.
CRey
9

Die Lösung, die ich mir ausgedacht habe, ist im Grunde dieselbe, die vor der Einführung von Storyboards verwendet wurde.

Erstellen Sie eine neue, leere Schnittstellenklassendatei. Ziehen Sie eine UIView auf die Leinwand, Layout wie gewünscht.

Laden Sie die Schreibfeder manuell und weisen Sie sie in den Delegate-Methoden viewForHeaderInSection oder viewForFooterInSection dem entsprechenden Kopf- / Fußzeilenabschnitt zu.

Ich hatte die Hoffnung, dass Apple dieses Szenario mit Storyboards vereinfachte und nach einer besseren oder einfacheren Lösung suchte. Zum Beispiel können benutzerdefinierte Tabellenkopf- und -fußzeilen einfach hinzugefügt werden.

Seamus
quelle
Nun, Apple hat es getan, wenn Sie die Storyboard-Zelle als Header-Methode verwenden :) stackoverflow.com/questions/9219234/#11396643
Tieme
2
Das funktioniert nur für Prototypzellen, nicht für statische Zellen.
Jean-Denis Muys
1
Eine wirklich einfache Lösung ist die Verwendung von Pixate . Sie können eine Menge Anpassungen vornehmen, ohne einen Verweis auf den Header zu erhalten. Alles, was Sie implementieren müssen tableView:titleForHeaderInSection, ist ein Einzeiler.
John Starr Dewar
5
Das ist die wirkliche Lösung ... leider ist es nicht oben, also habe ich eine Weile gebraucht, um es herauszufinden. Ich fasste die Probleme zusammen, die ich hatte hons82.blogspot.it/2014/05/uitableviewheader-done-right.html
Hons
Es ist einfacher als das, einfach per Drag & Drop.
Ricardo
5

Wenn Sie die Inhaltsansicht der Zelle zurückgeben, treten zwei Probleme auf:

  1. Absturz im Zusammenhang mit Gesten
  2. Sie verwenden contentView nicht wieder (jedes Mal viewForHeaderInSection, wenn Sie auf Abruf eine neue Zelle erstellen)

Lösung:

Wrapper-Klasse für Tabellenkopf \ Fußzeile. Es ist nur ein geerbter Container UITableViewHeaderFooterView, der die Zelle enthält

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

Registrieren Sie die Klasse in Ihrer UITableView (z. B. in viewDidLoad).

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

In Ihrem 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
quelle
2

Ich habe Folgendes getan, um träge Kopf- / Fußzeilenansichten zu erstellen:

  • Fügen Sie dem Storyboard einen Freiform-Ansichts-Controller für die Kopf- / Fußzeile des Abschnitts hinzu
  • Behandeln Sie alle Inhalte für den Header im View Controller
  • Stellen Sie in der Tabellenansichtssteuerung ein veränderbares Array von Ansichtssteuerungen für die mit neu gefüllten Abschnittsüberschriften / -fußzeilen bereit [NSNull null]
  • Wenn in viewForHeaderInSection / viewForFooterInSection noch kein Ansichts-Controller vorhanden ist, erstellen Sie ihn mit den Storyboards instantiateViewControllerWithIdentifier, merken Sie ihn sich im Array und geben Sie die Ansicht des Ansichts-Controllers zurück
Vipera Berus
quelle
2

Ich war in einem Szenario in Schwierigkeiten, in dem der Header nie wiederverwendet wurde, selbst wenn alle richtigen Schritte ausgeführt wurden.

Als Tipp für alle, die die Situation erreichen möchten, leere Abschnitte (0 Zeilen) anzuzeigen, sollten Sie Folgendes warnen:

dequeueReusableHeaderFooterViewWithIdentifier verwendet den Header erst wieder, wenn Sie mindestens eine Zeile zurückgeben

Ich hoffe es hilft

Juan Pedro Lozano
quelle
1

Um Damons Vorschlag zu folgen , habe ich den Header wie eine normale Zeile mit einem Offenlegungsindikator auswählbar gemacht.

Ich habe der Prototypzelle des Headers eine von UIButton untergeordnete Schaltfläche (Unterklassenname "ButtonWithArgument") hinzugefügt und den Titeltext gelöscht (der fett gedruckte "Titel" -Text ist ein weiteres UILabel in der Prototypzelle).

Schaltfläche im Interface Builder

Setzen Sie dann die Schaltfläche auf die gesamte Kopfansicht und fügen Sie mit Avarios Trick einen Offenlegungsindikator hinzu

- (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
quelle
1

Sie sollten die Lösung von Tieme als Basis verwenden, aber die viewWithTag:und andere fischartige Ansätze vergessen. Versuchen Sie stattdessen, Ihren Header neu zu laden (indem Sie diesen Abschnitt neu laden).

Nachdem Sie Ihre benutzerdefinierte Zellen-Header-Ansicht mit all den ausgefallenen AutoLayoutDingen eingerichtet haben, müssen Sie sie einfach aus der Warteschlange entfernen und die Inhaltsansicht nach der Einrichtung zurückgeben, z.

-(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
quelle
1

Was ist mit einer Lösung, bei der der Header auf einem Ansichtsarray basiert:

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

Weiter im Delegierten:

extension myViewController: UITableViewDelegate {
    func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        return(header[section])
    }
}
CyrIng
quelle
1
  1. Zelle hinzufügen StoryBoardund einstellenreuseidentified

    jdn

  2. Code

    class TP_TaskViewTableViewSectionHeader: UITableViewCell{
    }
    

    und

    Verknüpfung

  3. Verwenden:

    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
quelle
2
Dies ist nicht der richtige Weg, um einen Header hinzuzufügen
Manish Malviya
2
was ist dann der Weg?
Pramod Shukla
1

Ähnlich wie bei Laszlo Answer, aber Sie können dieselbe Prototypzelle sowohl für die Tabellenzellen als auch für die Abschnittskopfzelle wiederverwenden. Fügen Sie Ihrer UIViewController-Unterklasse die ersten beiden Funktionen hinzu

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
quelle
0

Hier ist die Antwort von @Vitaliy Gozhenko in Swift.
Zusammenfassend erstellen Sie eine UITableViewHeaderFooterView, die eine UITableViewCell enthält. Diese UITableViewCell ist "dequeuable" und Sie können sie in Ihrem Storyboard entwerfen.

  1. Erstellen Sie eine UITableViewHeaderFooterView-Klasse

    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. Fügen Sie Ihre Tabellenansicht mit dieser Klasse in Ihre viewDidLoad-Funktion ein:

    self.tableView.registerClass(CustomHeaderFooterView.self, forHeaderFooterViewReuseIdentifier: "SECTION_ID")
    
  3. Wenn Sie nach einem Abschnittsheader fragen, entfernen Sie eine CustomHeaderFooterView und fügen Sie eine Zelle ein

    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
quelle