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 UIImage
s, donc UIImageView
s 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
.
Je l'ai finalement résolu en utilisant ce tutoriel , qui consiste en grande partie comme suit (adapté à mon exemple):
SectionHeaderView
qui sous-classe UIView
.SectionHeaderView.xib
et définissez la variable CustomClass
de File's Owner
sur la classe SectionHeaderView
.UIView
dans le fichier .m
comme suit: @property (strong, nonatomic) IBOutlet UIView *viewContent;
View
de .xib
à cette sortie viewContent
.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 UIImageView
s 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.
Même Storyboard
:
return tableView.dequeueReusableCell(withIdentifier: "header")
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 .
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
.
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
}
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 .
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;
}
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.