web-dev-qa-db-fra.com

contentView ne se met pas en retrait dans la cellule prototype UITableViewCell iOS 6

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.

39
Skoota

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.

66
Skoota

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];
  }
}
32
Adrian

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
9
Mihaylov

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];
6
railwayparade

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).

2
Joseph Lord

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.

1