Jak zaimplementować niestandardowe nagłówki i stopki sekcji widoku tabeli za pomocą scenorysu


176

Bez użycia scenorysu moglibyśmy po prostu przeciągnąć UIViewna płótno, ułożyć je, a następnie ustawić w metodach tableView:viewForHeaderInSectionlub tableView:viewForFooterInSectiondelegate.

Jak to osiągnąć za pomocą StoryBoard, gdzie nie możemy przeciągnąć UIView na kanwę

Odpowiedzi:


91

Wiem, że to pytanie dotyczyło iOS 5, ale z korzyścią dla przyszłych czytelników zwróć uwagę, że efektywnego iOS 6 możemy teraz używać dequeueReusableHeaderFooterViewWithIdentifierzamiast dequeueReusableCellWithIdentifier.

Więc viewDidLoadzadzwoń albo registerNib:forHeaderFooterViewReuseIdentifier:albo registerClass:forHeaderFooterViewReuseIdentifier:. Następnie viewForHeaderInSectionzadzwoń tableView:dequeueReusableHeaderFooterViewWithIdentifier:. Nie używasz prototypu komórki z tym interfejsem API (jest to widok oparty na NIB lub widok utworzony programowo), ale jest to nowy interfejs API dla usuniętych z kolejki nagłówków i stopek.


13
westchnienie Szkoda, że ​​te dwie jednoznaczne odpowiedzi dotyczące używania NIB zamiast komórki proto mają obecnie mniej niż 5 głosów, a odpowiedź „hack it with proto cells” to ponad 200.
— Benjohn

5
Różnica polega na tym, że dzięki hackowi z Tieme możesz stworzyć swój projekt w tej samej planszy i nie używać osobnego Xib
— CedricSoubrie

Czy możesz w jakiś sposób uniknąć używania oddzielnego pliku NIB i zamiast tego użyć storyboardu?
— Foriger

@Foriger - Nie w tym momencie. To dziwne pominięcie w widokach tabeli scenariuszy, ale tak właśnie jest. Użyj tego nieznośnego hacka z komórkami prototypowymi, jeśli chcesz, ale osobiście po prostu pogodziłem się z irytacją NIB-ów związanych z widokami nagłówka / stopki.
— Rob

2
Samo powiedzenie, że jest „stary”, nie jest wystarczająco mocne, IMHO. Zaakceptowana odpowiedź jest niezgrabnym sposobem obejścia faktu, że nie, nie można mieć prototypowych komórek dla widoków nagłówka i stopki. Ale myślę, że jest to po prostu błędne, ponieważ zakłada, że ​​usuwanie z kolejki nagłówków i stopek działa tak samo, jak usuwanie z kolejki komórek (co sugeruje obecność nowszego interfejsu API dla nagłówków / stopek). Zaakceptowaną odpowiedzią jest kreatywne obejście, które było zrozumiałe w iOS 5, ale w iOS 6 i nowszych najlepiej jest używać interfejsu API specjalnie zaprojektowanego do ponownego wykorzystania nagłówka / stopki.
— Rob

384

Po prostu użyj prototypowej komórki jako nagłówka i / lub stopki sekcji.

  • dodaj dodatkową komórkę i umieść w niej żądane elementy.
  • ustaw identyfikator na coś konkretnego (w moim przypadku SectionHeader)
  • wdrożyć tableView:viewForHeaderInSection:metodę lub tableView:viewForFooterInSection:metodę
  • użyj, [tableView dequeueReusableCellWithIdentifier:]aby uzyskać nagłówek
  • wdrożyć tableView:heightForHeaderInSection:metodę.

(patrz zrzut ekranu)

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

Edycja: Jak zmienić tytuł nagłówka (skomentowane pytanie):

  1. Dodaj etykietę do komórki nagłówka
  2. ustawić znacznik etykiety na określony numer (np. 123)
  3. W swojej tableView:viewForHeaderInSection:metodzie uzyskaj etykietę, dzwoniąc:
    UILabel *label = (UILabel *)[headerView viewWithTag:123]; 
  1. Teraz możesz użyć etykiety, aby ustawić nowy tytuł:
    [label setText:@"New Title"];

7
Spóźniłem się na imprezę, ale aby wyłączyć kliknięcia w widoku nagłówka sekcji, po prostu zwróć cell.contentView w viewForHeaderInSection metodzie (nie ma potrzeby dodawania żadnych niestandardowych UIViews).
— paulvs

3
@PaulVon Próbowałem to zrobić w moim najnowszym projekcie, ale jeśli to zrobisz, po prostu spróbuj długo nacisnąć jeden ze swoich nagłówków, a ulegnie awarii
— Hons

13
W iOS 6.0 dequeueReusableHeaderFooterViewWithIdentifierzostał wprowadzony i jest teraz preferowany w stosunku do tej odpowiedzi. Ale użycie tego poprawnie wymaga teraz więcej kroków. Mam przewodnik @ samwize.com/2015/11/06/ ...
— samwize

3
Nie twórz swojej sekcji sectionHeaderViews przy użyciu UITableViewCells, spowoduje to nieoczekiwane zachowanie. Zamiast tego użyj UIView.
— AppreciateIt

4
Nigdy nie używaj UITableViewCelljako widoku nagłówka. Będziesz bardzo trudno debugować usterki wizualne - nagłówek czasami znika z powodu sposobu, w jaki komórki są usuwane z kolejki i będziesz szukał przez wiele godzin, dlaczego tak jest, dopóki nie zorientujesz się, UITableViewCellże nie należy do UITableViewnagłówka.
— raven_raven

53

W systemie iOS 6.0 i nowszym sytuacja uległa zmianie wraz z nowym dequeueReusableHeaderFooterViewWithIdentifierinterfejsem API.

Napisałem poradnik (testowany na iOS 9), który można podsumować następująco:

  1. Podklasa UITableViewHeaderFooterView
  2. Utwórz końcówkę z widokiem podklasy i dodaj 1 widok kontenera, który zawiera wszystkie inne widoki w nagłówku / stopce
  3. Zarejestruj stalówkę w viewDidLoad
  4. Zaimplementuj viewForHeaderInSectioni użyj, dequeueReusableHeaderFooterViewWithIdentifieraby odzyskać nagłówek / stopkę

2
Dziękuję za tę odpowiedź, która NIE jest hackem i za właściwy sposób robienia rzeczy. Dziękuję również za wprowadzenie mnie do UITableViewHeaderFooterVIew. Przy okazji, przewodnik jest po prostu doskonały! :)
— Roboris

Witamy. Implementacja nagłówka / stopki w widoku tabeli lub kolekcji nie jest dobrze udokumentowana przez firmę Apple. Jeszcze wczoraj utknąłem w nagłówku gettera, który pokazywał się podczas projektowania przez storyboad .
— samwize

1
To z pewnością powinna być najwyżej oceniana odpowiedź!
— Ben Williams

Czy mógłbyś wyjaśnić, jak to zrobić, używając storyboardu zamiast xib? Robiąc to, pomyślnie usunąłem z kolejki, ale moje etykiety UIL są zerowe.
— bunkerdive

22

Mam to działające w iOS7 przy użyciu prototypowej komórki w scenorysie. Mam przycisk w moim niestandardowym widoku nagłówka sekcji, który wyzwala przepływ, który jest skonfigurowany w scenorysie.

Zacznij od rozwiązania Tieme

Jak wskazuje pedro.m, problem polega na tym, że dotknięcie nagłówka sekcji powoduje wybranie pierwszej komórki w sekcji.

Jak podkreśla Paul Von, problem ten rozwiązano, zwracając contentView komórki zamiast całej komórki.

Jednak, jak wskazuje Hons, długie naciśnięcie nagłówka sekcji spowoduje awarię aplikacji.

Rozwiązaniem jest usunięcie wszystkich gestówRecognizers z 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; }

Jeśli nie używasz gestów w widokach nagłówków sekcji, wydaje się, że ten mały hack załatwia sprawę.


2
Masz rację. Właśnie przetestowałem to i jego działanie, więc zaktualizowałem swój post ( hons82.blogspot.it/2014/05/uitableviewheader-done-right.html ) zawierający wszystkie możliwe rozwiązania, które znalazłem podczas moich badań. Wielkie dzięki za tę poprawkę
— z wyróżnieniem

Dobra odpowiedź ... Dzięki Man
— Jogendra.Com

13

Jeśli używasz scenorysów, możesz użyć prototypowej komórki w widoku tabeli, aby ułożyć widok nagłówka. Ustaw unikatowy identyfikator i viewForHeaderInSection możesz usunąć z kolejki komórkę o tym identyfikatorze i rzucić ją na UIView.


12

Jeśli potrzebujesz szybkiej implementacji tego, postępuj zgodnie ze wskazówkami dotyczącymi zaakceptowanej odpowiedzi, a następnie UITableViewController zaimplementuj następujące metody:

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
Z jakiegoś powodu nie działało to dla mnie, dopóki nie nadpisałem również heightForHeaderInSection.
— Entalpi

To jest dość stary kod. powinieneś opublikować inną odpowiedź!
— JZ.

Utknąłem, ponieważ nie wiedziałem, że będę musiał zastąpić heightForHeaderInSection. Dzięki @Entalpi
— guijob

1
@J Z. W swoim przykładzie Swift 3 deque za pomocą następujących self.tableView.dequeueReusableHeaderFooterView i Swift 2: self.tableView.dequeueReusableCellWithIdentifier jest różnica?
— Vrutin Rathod

1
To uratowało mi tyłek! dodanie wysokości do headerCell sprawia, że ​​jest on widoczny.
— CRey

9

Rozwiązanie, które wymyśliłem, jest w zasadzie tym samym rozwiązaniem, które zastosowano przed wprowadzeniem storyboardów.

Utwórz nowy, pusty plik klasy interfejsu. Przeciągnij UIView na kanwę, układ według potrzeb.

Załaduj końcówkę ręcznie, przypisz do odpowiedniej sekcji nagłówka / stopki w metodach delegata viewForHeaderInSection lub viewForFooterInSection.

Miałem nadzieję, że Apple uprościło ten scenariusz dzięki scenorysom i szukał lepszego lub prostszego rozwiązania. Na przykład niestandardowe nagłówki i stopki tabel można łatwo dodać.


Cóż, Apple zrobił, jeśli użyjesz komórki storyboardowej jako metody nagłówka :) stackoverflow.com/questions/9219234/#11396643
— Tieme

2
Tyle że działa tylko w przypadku komórek prototypowych, a nie statycznych.
— Jean-Denis Muys

1
Naprawdę prostym rozwiązaniem jest użycie Pixate ; możesz zrobić całkiem sporo dostosowywania bez uzyskiwania odniesienia do nagłówka. Wszystko, co musisz zaimplementować tableView:titleForHeaderInSection, to jeden wiersz.
— John Starr Dewar

5
To jest prawdziwe rozwiązanie ... niestety nie jest na topie, więc zajęło mi trochę czasu, zanim się tego dowiedziałem. Podsumowałem problemy, które miałem hons82.blogspot.it/2014/05/uitableviewheader-done-right.html
— Hons

To łatwiejsze niż to, po prostu przeciągnij i upuść.
— Ricardo

5

Kiedy zwrócisz contentView komórki, będziesz miał 2 problemy:

  1. awaria związana z gestami
  2. nie używasz ponownie contentView (za każdym razem, gdy viewForHeaderInSectiondzwonisz, tworzysz nową komórkę)

Rozwiązanie:

Klasa opakowania dla nagłówka \ stopki tabeli. To tylko odziedziczony pojemnik UITableViewHeaderFooterView, w którym znajduje się komórka

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

Zarejestruj klasę w swoim UITableView (na przykład w viewDidLoad)

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

W Twoim 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

Kiedyś wykonywałem następujące czynności, aby leniwie tworzyć widoki nagłówka / stopki:

  • Dodaj kontroler widoku swobodnego dla nagłówka / stopki sekcji do serii ujęć
  • Obsługuj wszystkie rzeczy dla nagłówka w kontrolerze widoku
  • W widoku tabeli kontroler zapewnia zmienną tablicę kontrolerów widoku dla nagłówków / stopek sekcji, które są ponownie zapełnione [NSNull null]
  • W viewForHeaderInSection / viewForFooterInSection, jeśli kontroler widoku jeszcze nie istnieje, utwórz go za pomocą scenorysów instantiateViewControllerWithIdentifier, zapamiętaj go w tablicy i zwróć widok kontrolerów widoku

2

Miałem kłopoty w scenariuszu, w którym nagłówek nigdy nie został ponownie użyty, nawet wykonując wszystkie właściwe kroki.

Tak więc jako wskazówka dla wszystkich, którzy chcą osiągnąć sytuację pokazania pustych sekcji (0 wierszy), ostrzegamy, że:

dequeueReusableHeaderFooterViewWithIdentifier nie użyje ponownie nagłówka, dopóki nie zwrócisz co najmniej jednego wiersza

Mam nadzieję, że to pomoże


1

Aby podążać za sugestią Damona , oto jak sprawiłem, że nagłówek można wybrać, tak jak zwykły wiersz ze wskaźnikiem ujawnienia.

Dodałem podklasę Button z UIButton (nazwa podklasy „ButtonWithArgument”) do prototypowej komórki nagłówka i usunąłem tekst tytułu (pogrubiony tekst „Tytuł” ​​to kolejny UILabel w komórce prototypu)

Przycisk w programie Interface Builder

następnie ustaw przycisk na cały widok nagłówka i dodaj wskaźnik ujawnienia ze sztuczką 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

Powinieneś użyć rozwiązania Tieme jako bazy, ale zapomnij o viewWithTag:i innych podejrzanych podejściach, zamiast tego spróbuj przeładować nagłówek (przeładowując tę ​​sekcję).

Więc po ustawieniu niestandardowego widoku nagłówka komórki ze wszystkimi fantazyjnymi AutoLayoutrzeczami, po prostu usuń go z kolejki i zwróć contentView po konfiguracji, na przykład:

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

A co z rozwiązaniem, w którym nagłówek jest oparty na tablicy widoku:

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

Następnie w delegacie:

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

1
  1. Dodaj komórkę StoryBoardi ustawreuseidentified

    sb

  2. Kod

    class TP_TaskViewTableViewSectionHeader: UITableViewCell{
    }
    

    i

    połączyć

  3. Posługiwać się:

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

2
To nie jest właściwy sposób dodawania nagłówka
— Manish Malviya

2
co dalej?
— Pramod Shukla

1

Podobny do odpowiedzi laszlo, ale możesz ponownie użyć tej samej komórki prototypowej zarówno dla komórek tabeli, jak i komórki nagłówka sekcji. Dodaj pierwsze dwie funkcje poniżej do swojej podklasy 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

Oto odpowiedź @Vitaliy Gozhenko w języku Swift.
Podsumowując, utworzysz UITableViewHeaderFooterView, który zawiera UITableViewCell. Ta UITableViewCell będzie „usuwalna” i można ją zaprojektować w swoim storyboardzie.

  1. Utwórz klasę 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. Podłącz widok tabeli do tej klasy w funkcji viewDidLoad:

    self.tableView.registerClass(CustomHeaderFooterView.self, forHeaderFooterViewReuseIdentifier: "SECTION_ID")
    
  3. Pytając o nagłówek sekcji, usuń z kolejki CustomHeaderFooterView i wstaw do niego komórkę

    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;
    }
    
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.