web-dev-qa-db-fra.com

Concevez l'en-tête de section UITableView dans Interface Builder

J'ai un fichier xib avec une UITableView pour lequel je veux ajouter une vue d'en-tête de section personnalisée à l'aide de la méthode de délégation tableView:viewForHeaderInSection:. Est-il possible de le concevoir dans Interface Builder, puis de modifier certaines propriétés de sa sous-vue par programme?

Ma UITableView a plusieurs en-têtes de section, donc créer une UIView dans Interface Builder et le renvoyer ne fonctionnent pas, car je devrais le dupliquer, mais il n'y a pas de bonne méthode pour le faire. L'archivage et la désarchivage ne fonctionnent pas pour UIImages, donc UIImageViews serait vide.

De plus, je ne souhaite pas les créer par programme car ils sont trop complexes et que le code résultant serait difficile à lire et à maintenir.

Edit 1 : Voici ma méthode tableView:viewForHeaderInSection::

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {

    if ([tableView.dataSource tableView:tableView numberOfRowsInSection:section] == 0) {
        return nil;
    }

    CGSize headerSize = CGSizeMake(self.view.frame.size.width, 100);

    /* wrapper */

    UIView *wrapperView = [UIView viewWithSize:headerSize];

    wrapperView.backgroundColor = [UIColor colorWithHexString:@"2670ce"];

    /* title */

    CGPoint titleMargin = CGPointMake(15, 8);

    UILabel *titleLabel = [UILabel labelWithText:self.categoriesNames[section] andFrame:CGEasyRectMake(titleMargin, CGSizeMake(headerSize.width - titleMargin.x * 2, 20))];

    titleLabel.textColor = [UIColor whiteColor];
    titleLabel.font = [UIFont fontWithStyle:FontStyleRegular andSize:14];

    [wrapperView addSubview:titleLabel];

    /* body wrapper */

    CGPoint bodyWrapperMargin = CGPointMake(10, 8);

    CGPoint bodyWrapperViewOrigin = CGPointMake(bodyWrapperMargin.x, CGRectGetMaxY(titleLabel.frame) + bodyWrapperMargin.y);
    CGSize bodyWrapperViewSize = CGSizeMake(headerSize.width - bodyWrapperMargin.x * 2, headerSize.height - bodyWrapperViewOrigin.y - bodyWrapperMargin.y);

    UIView *bodyWrapperView = [UIView viewWithFrame:CGEasyRectMake(bodyWrapperViewOrigin, bodyWrapperViewSize)];

    [wrapperView addSubview:bodyWrapperView];

    /* image */

    NSInteger imageSize = 56;
    NSString *imageName = [self getCategoryResourceItem:section + 1][@"image"];

    UIImageView *imageView = [UIImageView imageViewWithImage:[UIImage imageNamed:imageName] andFrame:CGEasyRectMake(CGPointZero, CGEqualSizeMake(imageSize))];

    imageView.layer.masksToBounds = YES;
    imageView.layer.cornerRadius = imageSize / 2;

    [bodyWrapperView addSubview:imageView];

    /* labels */

    NSInteger labelsWidth = 60;

    UILabel *firstLabel = [UILabel labelWithText:@"first" andFrame:CGRectMake(imageSize + bodyWrapperMargin.x, 0, labelsWidth, 16)];

    [bodyWrapperView addSubview:firstLabel];

    UILabel *secondLabel = [UILabel labelWithText:@"second" andFrame:CGRectMake(imageSize + bodyWrapperMargin.x, 20, labelsWidth, 16)];

    [bodyWrapperView addSubview:secondLabel];

    UILabel *thirdLabel = [UILabel labelWithText:@"third" andFrame:CGRectMake(imageSize + bodyWrapperMargin.x, 40, labelsWidth, 16)];

    [bodyWrapperView addSubview:thirdLabel];

    [@[ firstLabel, secondLabel, thirdLabel ] forEachView:^(UIView *view) {
        UILabel *label = (UILabel *)view;

        label.textColor = [UIColor whiteColor];
        label.font = [UIFont fontWithStyle:FontStyleLight andSize:11];
    }];

    /* line */

    UIView *lineView = [UIView viewWithFrame:CGRectMake(imageSize + labelsWidth + bodyWrapperMargin.x * 2, bodyWrapperMargin.y, 1, bodyWrapperView.frame.size.height - bodyWrapperMargin.y * 2)];

    lineView.backgroundColor = [UIColor whiteColorWithAlpha:0.2];

    [bodyWrapperView addSubview:lineView];

    /* progress */

    CGPoint progressSliderOrigin = CGPointMake(imageSize + labelsWidth + bodyWrapperMargin.x * 3 + 1, bodyWrapperView.frame.size.height / 2 - 15);
    CGSize progressSliderSize = CGSizeMake(bodyWrapperViewSize.width - bodyWrapperMargin.x - progressSliderOrigin.x, 30);

    UISlider *progressSlider = [UISlider viewWithFrame:CGEasyRectMake(progressSliderOrigin, progressSliderSize)];

    progressSlider.value = [self getCategoryProgress];

    [bodyWrapperView addSubview:progressSlider];

    return wrapperView;
}

et je voudrais qu'il ressemble à ceci:

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {

    if ([tableView.dataSource tableView:tableView numberOfRowsInSection:section] == 0) {
        return nil;
    }

    SectionView *sectionView = ... // get the view that is already designed in the Interface Builder

    sectionView.headerText = self.categoriesNames[section];
    sectionView.headerImage = [self getCategoryResourceItem:section + 1][@"image"];

    sectionView.firstLabelText = @"first";
    sectionView.secondLabelText = @"second";
    sectionView.thirdLabelText = @"third";

    sectionView.progress = [self getCategoryProgress];

    return wrapperView;
}

Edit 2 : Je n'utilise pas une Storyboard, mais uniquement des fichiers .xib. De plus, je n'ai pas de UITableViewController, mais simplement un UIViewController dans lequel j'ai ajouté un UITableView.

26
Iulian Onofrei

Je l'ai finalement résolu en utilisant ce tutoriel , qui consiste en grande partie comme suit (adapté à mon exemple):

  1. Créez la classe SectionHeaderView qui sous-classe UIView.
  2. Créez le fichier SectionHeaderView.xib et définissez la variable CustomClass de File's Owner sur la classe SectionHeaderView.
  3. Créez une propriété UIView dans le fichier .m comme suit: @property (strong, nonatomic) IBOutlet UIView *viewContent;
  4. Connectez la View de .xib à cette sortie viewContent.
  5. Ajoutez une méthode d'initialisation qui ressemble à ceci:

    + (instancetype)header {
    
        SectionHeaderView *sectionHeaderView = [[SectionHeaderView alloc] init];
    
        if (sectionHeaderView) { // important part
            sectionHeaderView.viewContent = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:sectionHeaderView options:nil] firstObject];
    
            [sectionHeaderView addSubview:sectionHeaderView.viewContent];
    
            return sectionHeaderView;
        }
    
        return nil;
    }
    

Ensuite, j'ai ajouté une variable UILabel dans le fichier .xib et je l'ai connectée à la sortie labelCategoryName pour implémenter la méthode setCategoryName: dans la classe SectionHeaderView comme suit:

- (void)setCategoryName:(NSString *)categoryName {

    self.labelCategoryName.text = categoryName;
}

J'ai ensuite implémenté la méthode tableView:viewForHeaderInSection: comme ceci:

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {

    SectionHeaderView *sectionHeaderView = [SectionHeaderView header];

    [sectionHeaderView setCategoryName:self.categoriesNames[section]];

    return sectionHeaderView;
}

Et cela a finalement fonctionné. Chaque section a son propre nom, et UIImageViews s’affiche correctement.

J'espère que cela aidera d'autres personnes qui trébuchent sur les mêmes mauvaises solutions, partout sur le Web, comme je l'ai fait.

10
Iulian Onofrei

Storyboard ou XIB

  1. Même Storyboard:

    return tableView.dequeueReusableCell(withIdentifier: "header")
    

     Two Section Headers

  2. Séparez XIB (étape supplémentaire: vous devez d'abord enregistrer cette Nib): 

    tableView.register(UINib(nibName: "XIBSectionHeader", bundle:nil),
                       forCellReuseIdentifier: "xibheader")
    

Pour charger une Storyboard au lieu d'une XIB, voir cette réponse de débordement de pile .


Utilisation de UITableViewCell pour créer un en-tête de section dans IB

Tirez parti du fait qu’un en-tête de section est une UIView régulière et que UITableViewCell est également une UIView. Dans Interface Builder, faites glisser un cellule de la vue tableau depuis la bibliothèque d'objets sur votre contenu de prototype vue de table.

Ajoutez un Identifier à la _ {cellule de la table) récemment ajoutée et personnalisez son apparence en fonction de vos besoins. Pour cet exemple, j'ai utilisé header.

 Edit the cell

Utilisez dequeueReusableCell:withIdentifier pour localiser l'en-tête de votre section, comme vous le feriez pour toute cellule d'affichage de tableau. Vous devrez fournir heightForHeaderInSection, qui est codé en dur comme _ {44} _ pour plus de clarté:

//MARK: UITableViewDelegate
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView?
{
    // This is where you would change section header content
    return tableView.dequeueReusableCell(withIdentifier: "header")
}

override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat
{
    return 44
}

Swift 2 et plus tôt:

return tableView.dequeueReusableCellWithIdentifier("header") as? UIView
self.tableView.registerNib(UINib(nibName: "XIBSectionHeader", bundle:nil),
    forCellReuseIdentifier: "xibheader")

► Trouvez cette solution sur GitHub et des détails supplémentaires sur Swift Recipes .

76
SwiftArchitect

La solution est tellement simple 

Créez une xib, créez une interface utilisateur conformément à votre documentation .__ puis dans viewForHeaderInSection, obtenez xib

-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {

        NSArray *nibArray = [[NSBundle mainBundle] loadNibNamed:@"HeaderView" owner:self options:nil];

       HeaderView *headerView = [nibArray objectAtIndex:0];

    return headerView;
} 
1
Bevan

Dans la mesure où je comprends votre problème, vous souhaitez que le même UIView soit dupliqué plusieurs fois pour les en-têtes de sections multiples que vous souhaitez pouvoir afficher.

Si tel était mon problème, voici comment je le résoudrais.

SOLUTION ORIGINALE

1)

Dans mon UIViewController qui possède la vue de table, je créerais également une vue qui constitue un modèle pour l'en-tête. Attribuez-le à un IBOutlet. Ceci sera la vue que vous pourrez éditer via Interface Builder.

2)

Dans votre méthode ViewDidLoad ou (peut-être mieux) ViewWillAppear, vous souhaiterez créer autant de copies de ce modèle d'en-tête UIView qu'il vous faudra afficher pour les en-têtes de section.

Faire des copies de UIViews en mémoire n'est pas trivial, mais ce n'est pas difficile non plus. Voici une réponse d'une question connexe qui vous montre comment procéder.

Ajoutez les copies à une NSMutableArray (où l'index de chaque objet correspondra aux sections ... la vue dans l'index 0 du tableau sera celle que vous retournerez pour la section 0, la vue 1 du tableau pour la section 1, ec.) .

3)

Vous ne pourrez pas utiliser IBOutlets pour les éléments de l'en-tête de cette section (car votre code associe uniquement les points de vente à une vue particulière du fichier XIB).

Donc, pour cela, vous voudrez probablement utiliser les propriétés de balise de vue pour chacun des éléments d'interface utilisateur de votre vue d'en-tête que vous voudrez modifier/changer pour chaque section différente. Vous pouvez définir ces balises via Interface Builder, puis y faire référence par programme dans votre code.

Dans votre méthode viewForHeaderInSection, vous ferez quelque chose comme:

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {

    if ([tableView.dataSource tableView:tableView numberOfRowsInSection:section] == 0) {
        return nil;
    }

    SectionView *sectionView = [self.arrayOfDuplicatedHeaderViews objectAtIndex: section];
    // my title view has a tag of 10
    UILabel *titleToModify = [sectionView viewWithTag: 10]; 
    if(titleToModify)
    {
        titleToModify.text = [NSString stringWithFormat:@"section %d", section];
    }

    return sectionView;
}

Logique?

DIFFERENT SOLUTION

1)

Vous auriez toujours besoin d'un tableau de UIViews (ou de UIViews sous-classés "Section View"), mais vous pouvez créer chacun de ceux-ci avec des appels successifs pour charger la vue à partir de son propre fichier XIB. 

Quelque chose comme ça:

@implementation SectionView

+ (SectionView*) getSectionView
{
  NSArray* array = [[NSBundle mainBundle] loadNibNamed:@"SectionView" owner:nil options:nil];
  return [array objectAtIndex:0]; // assume that SectionView is the only object in the xib
}

@end

(plus de détails trouvés dans la réponse à cette question connexe )

2)

Vous pourriez pouvoir utiliser IBOutlets sur ce point (mais je ne suis pas sûr à 100%), mais les propriétés des balises fonctionneraient encore une fois plutôt bien. 

0
Michael Dautermann