2012-09-26 15 views
38

Estoy configurando un UITableViewCell personalizado usando una celda prototipo en un guión gráfico. Sin embargo, todos los UILabel s (y otros elementos de la IU) no parecen agregarse a la celda contentView, sino que se agregan directamente a la vista UITableViewCell. Esto crea problemas cuando la celda se pone en modo de edición, ya que el contenido no se cambia/sangra automáticamente (lo que haría, si estuvieran dentro del contentView).contentView no sangría en iOS 6 Celda prototipo UITableViewCell

¿Hay alguna forma de agregar los elementos de la interfaz de usuario al contentView al diseñar la celda mediante Interface Builder/Storyboard/prototype cells? La única forma que he encontrado es crear todo en código y usar [cell.contentView addSubView:labelOne] que no sería genial, ya que es mucho más fácil diseñar la celda gráficamente.

+1

¿Estás seguro de eso? La última vez que distribuí una celda en una plumilla, aunque parecía que no estaba agregando vistas secundarias a la vista de contenido, en el tiempo de ejecución en el depurador todo estaba en la vista de contenido. Vale la pena verificar en el depurador si aún no lo has hecho ... –

+1

Gracias Carl. Tenías razón: todas las subvistas se agregaron a contentView. El problema estaba relacionado con el autolayout de iOS 6. He incluido una respuesta que describe cómo se solucionó el problema. – Skoota

Respuesta

66

En una investigación más a fondo (ver la jerarquía de la subvista de la celda) Interface Builder coloca las subvistas dentro de la celda contentView, simplemente no se ve así.

La causa principal del problema fue iOS 6 autolayout. Cuando la celda se coloca en modo de edición (y sangría), el contentView también está sangrado, por lo que es lógico pensar que todas las subvistas dentro del contentView se moverán (sangría) en virtud de estar dentro del contentView. Sin embargo, todas las restricciones de autolayout aplicadas por Interface Builder parecen ser relativas al UITableViewCell, en lugar del contentView. Esto significa que, aunque las sangrías contentView, las subvistas contenidas en las restricciones no se hacen cargo.

Por ejemplo, cuando coloqué un UILabel en la celda (y lo coloqué a 10 puntos desde el lado izquierdo de la celda), IB aplicó automáticamente una restricción "Espacio horizontal (10)". Sin embargo, esta restricción es relativa al UITableViewCell NO al contentView. Esto significa que cuando se sangra la celda y se mueve el contentView, la etiqueta se mantiene puesta ya que cumple con la restricción de permanecer 10 puntos desde el lado izquierdo del UITableViewCell.

Lamentablemente (hasta donde yo sé) no hay forma de eliminar estas restricciones creadas por IB desde dentro de IB, así que así es como resolví el problema.

Dentro de la subclase UITableViewCell para la celda, creé un IBOutlet para esa restricción llamada cellLabelHSpaceConstraint. También necesita un IBOutlet para la etiqueta en sí, que llamé al cellLabel. entonces he implementado el método de -awakeFromNib según abajo:

- (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 resumen, lo anterior se eliminará la restricción de espaciado horizontal que añade automáticamente IB (como es eficaz contra la UITableViewCell en lugar de la contentView) y que a continuación, definir y añadir nuestra propia restricción al contentView.

En mi caso, todos los otros UILabels en la celda se colocaron según la posición del cellLabel, así que cuando arreglé la restricción/posicionamiento de este elemento, todos los demás lo siguieron y colocaron correctamente. Sin embargo, si tiene un diseño más complejo, puede que necesite hacer esto para otras subvistas también.

+2

+1 descubrimiento y solución muy interesantes! –

+0

Gracias por publicar su solución alternativa detallada. Tenía el mismo problema! – Prine

+3

Puede solucionar esto desactivando el diseño automático en el generador de interfaz, ya sea para todo el guión gráfico o solo para el plumín que contiene su celda. Vea la respuesta aquí para más detalles: http://stackoverflow.com/questions/12833176/indentation-not-working-on-custom-uitableviewcell – jrturton

6

Una alternativa a la creación de subclases es revisar las restricciones en cellForRowAtIndexPath.

Incruste todo el contenido de la celda dentro de una vista de contenedor. Luego, señale las restricciones inicial y final a cell.contentView en lugar de a la celda de la vista de tabla.

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]; 
9

Aquí es una subclase, sobre la base de otras ideas respuestas, voy a basar mis células personalizados en:

@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 
+0

En mi opinión, no tiene sentido llamar a initFixedTableViewCell desde initWithStyle: reuseIdentifier: como es un error de Interface Builder, no puede ocurrir solo si carga el cel de un archivo de punta. Esperemos que en el código, uno no debe reproducir el error de Interface Builder ;-) Tampoco veo por qué verificar para los descendientes en la lista de restricciones. – Adrian

+2

Esto funcionó bien para mí, pero también debe preservar la prioridad de restricción: "contentViewConstraint.priority = constraint.priority" –

32

Como se ha mencionado, Interface Builder de XCode se esconde la contentView de UITableViewCell. En realidad, todos los elementos de UI añadidos a UITableViewCell son de hecho subvistas de contentView.

Por el momento, IB no está haciendo la misma magia para las restricciones de diseño, lo que significa que están expresadas en el nivel UITableViewCell.

Una solución consiste en awakeFromNib de una subclase de mover todos NSAutoLayoutConstrains de UITableViewCell a que es contentView y expresarlos en términos de la 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]; 
    } 
} 
+0

¡Esto funcionó para mí! ¡Gracias! – JimmyJammed

+0

Esto funcionó muy bien! Estaba usando una vista de tabla agrupada y tenía que hacer todo lo relacionado con la celda en el constructor de interfaz en lugar de cómo se veía visualmente, ¡pero funcionó para que se aplicara una sangría y un clip correctamente! – Gujamin

+0

+1 Agregué esto a mi base 'UITableViewCell' así que nunca más tendré que lidiar con esto otra vez. – memmons

2

Creo que esto se fija en iOS 7 beta 3 haciendo las soluciones innecesaria desde ese momento (pero probablemente inofensivo ya que en la mayoría de los casos se convertirán en operaciones vacías).

1

Basado en el código de Skoota (soy un principiante, no sé mucho de lo que hizo, pero el trabajo es excelente), mi sugerencia es poner todas sus cosas en una vista de contenedor de borde a borde y agregar el siguiente:

en el archivo de cabecera de la célula, tengo los siguientes: IBOutlets

@property (weak, nonatomic) IBOutlet UIView *container; 
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *leftConstrain; 
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *rightConstrain; 

en el archivo de implementación, tengo el siguiente en 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]; 

Una vez más, la arriba fue posible gracias a Skoota. ¡¡¡Gracias!!! Los créditos de Al van a él.

Cuestiones relacionadas