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ę

Seamus
źródło

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.

Obrabować
źródło
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"];
Zwiąż mnie
źródło
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ę
samwize
źródło
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ę.

damon
źródło
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.

barksten
źródło
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
}
J Z.
źródło
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ć.

Seamus
źródło
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;
}
Vitaliy Gozhenko
źródło
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
Vipera Berus
źródło
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

Juan Pedro Lozano
źródło
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
MarkF
źródło
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;
}  
Laszlo
źródło
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])
    }
}
CyrIng
źródło
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
    }
    
leo.tan
źródło
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
}
Richard Legault
źródło
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;
    }
    
CedricSoubrie
źródło