web-dev-qa-db-fra.com

Comment implémenter des en-têtes et des pieds de section de vue de tableau personnalisés avec Storyboard

Sans utiliser de story-board, nous pourrions simplement faire glisser une UIView sur le canevas, la disposer puis la définir dans les méthodes de délégué tableView:viewForHeaderInSection ou tableView:viewForFooterInSection.

Comment pouvons-nous accomplir cela avec un StoryBoard où nous ne pouvons pas faire glisser UIView sur la toile

170
Seamus

Je sais que cette question était pour iOS 5, mais pour le bénéfice des futurs lecteurs, notez qu'effectivement iOS 6, nous pouvons maintenant utiliser dequeueReusableHeaderFooterViewWithIdentifier au lieu de dequeueReusableCellWithIdentifier.

Donc, dans viewDidLoad, appelez soit registerNib:forHeaderFooterViewReuseIdentifier: ou registerClass:forHeaderFooterViewReuseIdentifier:. Ensuite, dans viewForHeaderInSection, appelez tableView:dequeueReusableHeaderFooterViewWithIdentifier:. Vous n'utilisez pas de prototype de cellule avec cette API (c'est une vue basée sur une carte NIB ou une vue créée par programme), mais il s'agit de la nouvelle API pour les en-têtes et les pieds de page supprimés.

85
Rob

Utilisez simplement une cellule prototype comme en-tête et/ou pied de section. 

  • ajoutez une cellule supplémentaire et mettez-y les éléments souhaités. 
  • définir l'identifiant sur quelque chose de spécifique (dans mon cas, SectionHeader)
  • implémenter la méthode tableView:viewForHeaderInSection: ou la méthode tableView:viewForFooterInSection:
  • utiliser [tableView dequeueReusableCellWithIdentifier:] pour obtenir l'en-tête
  • implémenter la méthode tableView:heightForHeaderInSection:.

(see screenhot)

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

Modifier: Comment changer le titre de l'en-tête (question commentée):

  1. Ajouter une étiquette à la cellule d'en-tête
  2. définir l’étiquette de l’étiquette sur un numéro spécifique (par exemple 123)
  3. Dans votre méthode tableView:viewForHeaderInSection:, obtenez l'étiquette en appelant:
    UILabel *label = (UILabel *)[headerView viewWithTag:123]; 
  1. Vous pouvez maintenant utiliser le libellé pour définir un nouveau titre:
    [label setText:@"New Title"];
379
Tieme

Dans iOS 6.0 et les versions ultérieures, les choses ont changé avec la nouvelle API dequeueReusableHeaderFooterViewWithIdentifier.

J'ai écrit un guide (testé sur iOS 9), qui peut être résumé comme suit:

  1. Sous-classe UITableViewHeaderFooterView
  2. Créer un nib avec la vue sous-classe et ajouter 1 vue conteneur qui contient toutes les autres vues dans l'en-tête/le pied de page
  3. Enregistrez le Nib dans viewDidLoad
  4. Implémentez viewForHeaderInSection et utilisez dequeueReusableHeaderFooterViewWithIdentifier pour récupérer l'en-tête/le pied de page
52
samwize

Je l'ai fait fonctionner dans iOS7 en utilisant un prototype de cellule dans le storyboard. J'ai un bouton dans ma vue d'en-tête de section personnalisée qui déclenche une séquence configurée dans le storyboard.

Commencez avec La solution de Tieme

Comme le souligne pedro.m, le problème est que, si vous appuyez sur l'en-tête de la section, la première cellule de la section est sélectionnée. 

Comme le souligne Paul Von, le problème est résolu en renvoyant le contenu de la cellule à la place de la totalité de la cellule.

Cependant, comme le souligne Hons, un appui long sur l'en-tête de cette section plantera l'application.

La solution consiste à supprimer tous les gestureRecognizers de 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; }

Si vous n'utilisez pas de gestes dans vos vues d'en-tête de section, ce petit bidouillage semble le faire.

22
damon

Si vous utilisez des storyboards, vous pouvez utiliser une cellule prototype dans la vue tableau pour mettre en forme votre vue d'en-tête. Définissez un identifiant unique et viewForHeaderInSection que vous pouvez retirer de la cellule avec cet ID et le convertir en UIView.

13
barksten

Si vous avez besoin d’une implémentation rapide de cette opération, suivez les instructions de la réponse acceptée, puis appliquez UITableViewController aux méthodes suivantes:

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
}
10
JZ.

La solution que j'ai proposée est fondamentalement la même que celle utilisée avant l'introduction des scénarimages.

Créez un nouveau fichier de classe d'interface vide. Faites glisser un UIView sur le canevas, mettez-le comme vous le souhaitez.

Chargez le nib manuellement, attribuez-le à la section d’en-tête/pied de page appropriée dans les méthodes de délégation viewForHeaderInSection ou viewForFooterInSection.

J'espérais qu'Apple simplifiait ce scénario avec des storyboards et continuait à chercher une solution meilleure ou plus simple. Par exemple, les en-têtes de table et les pieds de page personnalisés sont faciles à ajouter.

9
Seamus

Lorsque vous renvoyez le contenu de la cellule, vous aurez 2 problèmes:

  1. crash lié aux gestes
  2. vous ne réutilisez pas contentView (chaque fois que vous appelez viewForHeaderInSection, vous créez une nouvelle cellule)

Solution:

Classe wrapper pour l'en-tête de table\footer . Il ne s'agit que d'un conteneur, hérité de UITableViewHeaderFooterView, qui contient la cellule à l'intérieur

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

Enregistrez la classe dans votre UITableView (par exemple, dans viewDidLoad)

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

Dans votre 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;
}
5
Vitaliy Gozhenko

Je faisais les choses suivantes pour créer des vues d'en-tête/pied de page paresseusement:

  • Ajouter un contrôleur de vue de forme libre pour l'en-tête/le pied de section de la maquette
  • Gérer tous les éléments de l'en-tête dans le contrôleur de vue
  • Dans le tableau, le contrôleur de vue fournit un tableau modifiable de contrôleurs de vue pour les en-têtes de section/pieds de page remplis avec [NSNull null]
  • Dans viewForHeaderInSection/viewForFooterInSection si le contrôleur de vue n'existe pas encore, créez-le avec les storyboards instantiateViewControllerWithIdentifier, mémorisez-le dans le tableau et retournez la vue des contrôleurs de vue.
2
Vipera Berus

J'ai eu des problèmes dans un scénario où Header n'a jamais été réutilisé, même en effectuant toutes les étapes appropriées.

En guise de conseil à tous ceux qui souhaitent atteindre la situation suivante, montrez les sections vides (0 lignes):

dequeueReusableHeaderFooterViewWithIdentifier ne réutilisera pas l'en-tête tant que vous n'aurez pas renvoyé au moins une ligne

J'espère que ça aide

2

Pour faire suite à la question de { suggestion de Damon }, voici comment j'ai sélectionné l'en-tête sélectionnable, tout comme une ligne normale avec un indicateur de divulgation.

J'ai ajouté un bouton sous-classé dans UIButton (nom de la sous-classe "ButtonWithArgument") dans la cellule prototype de l'en-tête et supprimé le texte du titre (le texte en gras "Titre" est un autre UILabel dans la cellule du prototype).

Button In Interface Builder

puis définissez le bouton sur l'intégralité de la vue en-tête et ajoutez un indicateur de divulgation avec astuce d'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
MarkF

Qu'en est-il d'une solution où l'en-tête est basé sur un tableau de vues:

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

Suivant dans le délégué:

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

Vous devriez utiliser la solution de Tieme comme base, mais oubliez le viewWithTag: et les autres approches ludiques, essayez plutôt de recharger votre en-tête (en rechargeant cette section).

Ainsi, une fois que vous avez créé votre vue personnalisée en-tête de cellule avec tous les éléments fantaisie AutoLayout, il suffit de la retirer de la file d'attente et de renvoyer le contentView après votre configuration, comme suit:

-(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
Laszlo
  1. Ajouter une cellule dans StoryBoard et définir reuseidentified 

     sb

  2. Code 

    class TP_TaskViewTableViewSectionHeader: UITableViewCell{
    }
    

    et 

     link

  3. Utilisation:

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

Voici la réponse de @Vitaliy Gozhenko , dans Swift.
En résumé, vous allez créer un UITableViewHeaderFooterView contenant un UITableViewCell. Ce UITableViewCell sera "dequeuable" et vous pouvez le concevoir dans votre storyboard.

  1. Créer une 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. Branchez votre tableview avec cette classe dans votre fonction viewDidLoad:

    self.tableView.registerClass(CustomHeaderFooterView.self, forHeaderFooterViewReuseIdentifier: "SECTION_ID")
    
  3. Lorsque vous demandez un en-tête de section, supprimez le CustomHeaderFooterView et insérez-y une cellule

    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;
    }
    
0
CedricSoubrie

Identique à laszlo answer, mais vous pouvez réutiliser le même prototype de cellule pour les cellules de tableau et la cellule d’en-tête de section. Ajoutez les deux premières fonctions ci-dessous à votre sous-classe 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
Richard Legault