Je configure un UITableViewCell
personnalisé à l'aide d'une cellule prototype dans un Storyboard. Cependant, tous les UILabel
(et autres éléments d'interface utilisateur) ne semblent pas être ajoutés à la cellule contentView
de la cellule, au lieu d'être ajoutés directement à la vue UITableViewCell
. Cela crée des problèmes lorsque la cellule est mise en mode édition, car le contenu n'est pas automatiquement décalé/indenté (ce qu'il ferait, s'il était à l'intérieur du contentView
).
Existe-t-il un moyen d'ajouter les éléments d'interface utilisateur au contentView
lors de la disposition de la cellule à l'aide d'Interface Builder/Storyboard/prototype cells? La seule façon que j'ai trouvée est de tout créer en code et d'utiliser [cell.contentView addSubView:labelOne]
ce qui ne serait pas génial, car il est beaucoup plus facile d'agencer graphiquement la cellule.
Lors d'une enquête plus approfondie (affichage de la hiérarchie des sous-vues de la cellule), Interface Builder place les sous-vues dans le contentView
de la cellule, il ne lui ressemble tout simplement pas.
La cause principale du problème était la mise en page automatique iOS 6. Lorsque la cellule est placée en mode édition (et en retrait), le contentView
est également en retrait, il va donc de soi que toutes les sous-vues dans le contentView
se déplaceront (en retrait) du fait qu'elles se trouvent dans le contentView
. Cependant, toutes les contraintes de mise en page automatique appliquées par Interface Builder semblent être relatives au UITableViewCell
lui-même, plutôt qu'au contentView
. Cela signifie que même si les retraits contentView
, les sous-vues qu'ils contiennent ne le font pas - les contraintes prennent en charge.
Par exemple, lorsque j'ai placé un UILabel
dans la cellule (et que je l'ai positionné à 10 points du côté gauche de la cellule), IB a automatiquement appliqué une contrainte "Espace horizontal (10)". Cependant, cette contrainte est relative au UITableViewCell
PAS au contentView
. Cela signifie que lorsque la cellule est en retrait et que le contentView
se déplace, l'étiquette reste en place car elle respecte la contrainte de rester à 10 points du côté gauche du UITableViewCell
.
Malheureusement (pour autant que je sache), il n'y a aucun moyen de supprimer ces contraintes créées par IB de IB lui-même, alors voici comment j'ai résolu le problème.
Dans la sous-classe UITableViewCell
de la cellule, j'ai créé une IBOutlet
pour cette contrainte appelée cellLabelHSpaceConstraint
. Vous avez également besoin d'un IBOutlet
pour l'étiquette elle-même, que j'ai appelée cellLabel
. J'ai ensuite implémenté le -awakeFromNib
méthode selon ci-dessous:
- (void)awakeFromNib {
// -------------------------------------------------------------------
// We need to create our own constraint which is effective against the
// contentView, so the UI elements indent when the cell is put into
// editing mode
// -------------------------------------------------------------------
// Remove the IB added horizontal constraint, as that's effective
// against the cell not the contentView
[self removeConstraint:self.cellLabelHSpaceConstraint];
// Create a dictionary to represent the view being positioned
NSDictionary *labelViewDictionary = NSDictionaryOfVariableBindings(_cellLabel);
// Create the new constraint
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"|-10-[_cellLabel]" options:0 metrics:nil views:labelViewDictionary];
// Add the constraint against the contentView
[self.contentView addConstraints:constraints];
}
En résumé, ce qui précède supprimera la contrainte d'espacement horizontal que IB a automatiquement ajoutée (comme c'est efficace contre le UITableViewCell
plutôt que le contentView
) et nous définissons ensuite et ajoutons notre propre contrainte au contentView
.
Dans mon cas, tous les autres UILabels
dans la cellule ont été positionnés en fonction de la position du cellLabel
donc quand j'ai corrigé la contrainte/positionnement de cet élément, tous les autres ont emboîté le pas et se sont positionnés correctement . Cependant, si vous avez une mise en page plus complexe, vous devrez peut-être le faire également pour d'autres sous-vues.
Comme mentionné, le générateur d'interface de XCode masque le contentView de UITableViewCell. En réalité, tous les éléments d'interface utilisateur ajoutés à UITableViewCell sont en fait des sous-vues de contentView.
Pour l'instant, il ne fait pas la même magie pour les contraintes de mise en page, ce qui signifie qu'elles sont toutes exprimées au niveau UITableViewCell.
Une solution de contournement est dans awakeFromNib d'une sous-classe pour déplacer tous les NSAutoLayoutConstrains de UITableViewCell vers son contentView et les exprimer en termes de contentView:
-(void)awakeFromNib{
[super awakeFromNib];
for(NSLayoutConstraint *cellConstraint in self.constraints){
[self removeConstraint:cellConstraint];
id firstItem = cellConstraint.firstItem == self ? self.contentView : cellConstraint.firstItem;
id seccondItem = cellConstraint.secondItem == self ? self.contentView : cellConstraint.secondItem;
NSLayoutConstraint* contentViewConstraint =
[NSLayoutConstraint constraintWithItem:firstItem
attribute:cellConstraint.firstAttribute
relatedBy:cellConstraint.relation
toItem:seccondItem
attribute:cellConstraint.secondAttribute
multiplier:cellConstraint.multiplier
constant:cellConstraint.constant];
[self.contentView addConstraint:contentViewConstraint];
}
}
Voici une sous-classe, basée sur d'autres idées de réponses, je vais baser mes cellules personnalisées sur:
@interface FixedTableViewCell ()
- (void)initFixedTableViewCell;
@end
@interface FixedTableViewCell : UITableViewCell
@end
@implementation FixedTableViewCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
if (nil != (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier])) {
[self initFixedTableViewCell];
}
return self;
}
- (void)awakeFromNib {
[super awakeFromNib];
[self initFixedTableViewCell];
}
- (void)initFixedTableViewCell {
for (NSInteger i = self.constraints.count - 1; i >= 0; i--) {
NSLayoutConstraint *constraint = [self.constraints objectAtIndex:i];
id firstItem = constraint.firstItem;
id secondItem = constraint.secondItem;
BOOL shouldMoveToContentView = YES;
if ([firstItem isDescendantOfView:self.contentView]) {
if (NO == [secondItem isDescendantOfView:self.contentView]) {
secondItem = self.contentView;
}
}
else if ([secondItem isDescendantOfView:self.contentView]) {
if (NO == [firstItem isDescendantOfView:self.contentView]) {
firstItem = self.contentView;
}
}
else {
shouldMoveToContentView = NO;
}
if (shouldMoveToContentView) {
[self removeConstraint:constraint];
NSLayoutConstraint *contentViewConstraint = [NSLayoutConstraint constraintWithItem:firstItem
attribute:constraint.firstAttribute
relatedBy:constraint.relation
toItem:secondItem
attribute:constraint.secondAttribute
multiplier:constraint.multiplier
constant:constraint.constant];
[self.contentView addConstraint:contentViewConstraint];
}
}
}
@end
Une alternative au sous-classement consiste à réviser les contraintes dans cellForRowAtIndexPath.
Incorporez tout le contenu de la cellule dans une vue de conteneur. Pointez ensuite les contraintes de début et de fin sur cell.contentView plutôt que sur la cellule de vue de table.
UIView *containerView = [cell viewWithTag:999];
UIView *contentView = [cell contentView];
//remove existing leading and trailing constraints
for(NSLayoutConstraint *c in [cell constraints]){
if(c.firstItem==containerView && (c.firstAttribute==NSLayoutAttributeLeading || c.firstAttribute==NSLayoutAttributeTrailing)){
[cell removeConstraint:c];
}
}
NSLayoutConstraint *trailing = [NSLayoutConstraint
constraintWithItem:containerView
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:contentView
attribute:NSLayoutAttributeTrailing
multiplier:1
constant:0];
NSLayoutConstraint *leading = [NSLayoutConstraint
constraintWithItem:containerView
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:contentView
attribute:NSLayoutAttributeLeading
multiplier:1
constant:0];
[cell addConstraint:trailing];
[cell addConstraint:leading];
Je pense que cela est corrigé dans iOS 7 beta 3 rendant les solutions de contournement inutiles à partir de ce moment (mais probablement inoffensives car dans la plupart des cas, elles deviendront des opérations vides).
Basé sur le code de Skoota (je suis un débutant, je ne sais pas grand-chose de ce que vous avez fait, mais excellent travail) ma suggestion est de mettre toutes vos affaires dans une vue de conteneur Edge-to-Edge et d'ajouter ce qui suit:
Dans le fichier d'en-tête de la cellule, j'ai les IBOutlets suivants:
@property (weak, nonatomic) IBOutlet UIView *container;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *leftConstrain;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *rightConstrain;
Dans le fichier d'implémentation, j'ai les éléments suivants dans awakeFromNib:
// Remove the IB added horizontal constraint, as that's effective gainst the cell not the contentView
[self removeConstraint:self.leftConstrain];
[self removeConstraint:self.rightConstrain];
// Create a dictionary to represent the view being positioned
NSDictionary *containerViewDictionary = NSDictionaryOfVariableBindings(_container);
// Create the new left constraint (0 spacing because of the Edge-to-Edge view 'container')
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"|-0-[_container]" options:0 metrics:nil views:containerViewDictionary];
// Add the left constraint against the contentView
[self.contentView addConstraints:constraints];
// Create the new constraint right (will fix the 'Delete' button as well)
constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"[_container]-0-|" options:0 metrics:nil views:containerViewDictionary];
// Add the right constraint against the contentView
[self.contentView addConstraints:constraints];
Encore une fois, ce qui précède a été rendu possible par Skoota. Merci!!! Tous les crédits lui reviennent.