2012-09-26 14 views
38

Sto configurando un modello personalizzato UITableViewCell utilizzando una cella prototipo in uno Storyboard. Tuttavia, tutti gli UILabel s (e altri elementi dell'interfaccia utente) non sembrano essere aggiunti al numero contentView della cella, ma vengono aggiunti direttamente alla vista UITableViewCell. Questo crea problemi quando la cella viene messa in modalità di modifica, in quanto il contenuto non viene spostato/rientrato automaticamente (cosa che farebbe se si trovassero all'interno dello contentView).contentView non rientri in iOS 6 UITableViewCell cellula prototipo

C'è un modo per aggiungere gli elementi dell'interfaccia utente allo contentView quando si espande la cella utilizzando Interface Builder/Storyboard/prototype cells? L'unico modo che ho trovato è quello di creare tutto nel codice e utilizzare [cell.contentView addSubView:labelOne] che non sarebbe eccezionale, in quanto è molto più semplice il layout grafico della cella.

+1

Ne sei sicuro? L'ultima volta che ho disposto una cella in un pennino, mentre non sembrava che aggiungessi viste secondarie alla vista del contenuto, in fase di esecuzione nel debugger tutto era nella visualizzazione del contenuto. Vale la pena verificare nel debugger se non l'hai già ... –

+1

Grazie Carl. Avevi ragione: tutte le visualizzazioni secondarie vengono aggiunte a contentView. Il problema era correlato all'autosayout di iOS 6. Ho incluso una risposta che illustra come il problema è stato risolto. – Skoota

risposta

66

Su ulteriori indagini (visualizzazione della gerarchia di sottomenu della cella) Interface Builder inserisce subview all'interno della cella contentView, semplicemente non sembra.

La causa principale del problema era l'autolayout di iOS 6. Quando la cella viene posizionata in modalità di modifica (e rientrata), anche lo contentView è rientrato, quindi è ovvio che tutte le sottoview all'interno dello contentView si sposteranno (rientro) in virtù del fatto che si trova all'interno dello contentView. Tuttavia, tutti i vincoli di autolayout applicati da Interface Builder sembrano essere relativi allo stesso UITableViewCell, anziché allo contentView. Ciò significa che, nonostante i rientri contentView, le immagini secondarie contenute all'interno non lo fanno, i vincoli prendono il sopravvento.

Ad esempio, quando ho inserito uno UILabel nella cella (e posizionato 10 punti dal lato sinistro della cella) IB ha applicato automaticamente un vincolo "Spazio orizzontale (10)". Tuttavia, questo vincolo è relativo allo UITableViewCell NON allo contentView. Ciò significa che quando la cella è rientrata e lo contentView si sposta, l'etichetta rimane inserita poiché rispetta il vincolo per rimanere 10 punti dal lato sinistro dello UITableViewCell.

Sfortunatamente (per quanto ne so) non c'è modo di rimuovere questi vincoli creati da IB all'interno di IB stesso, quindi ecco come ho risolto il problema.

All'interno della sottoclasse UITableViewCell per la cella, ho creato uno IBOutlet per quel vincolo chiamato cellLabelHSpaceConstraint. È inoltre necessario un IBOutlet per l'etichetta stessa, che ho chiamato cellLabel. Poi implementato il metodo -awakeFromNib come sotto:

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

} 

In sintesi, il sopra rimuoverà il vincolo orizzontale spaziatura che IB aggiunto automaticamente (come è efficace contro il UITableViewCell anziché contentView) e quindi definire e aggiungi il nostro vincolo allo contentView.

Nel mio caso, tutti gli altri UILabels nella cella sono stati posizionati in base alla posizione dello cellLabel così quando ho corretto il vincolo/posizionamento di questo elemento tutti gli altri hanno seguito l'esempio e posizionato correttamente. Tuttavia, se si dispone di un layout più complesso, potrebbe essere necessario farlo anche per altri sottoview.

+2

+1 scoperta e soluzione molto interessanti! –

+0

Grazie per aver postato la tua soluzione dettagliata. Ho avuto lo stesso problema! – Prine

+3

Puoi aggirare questo problema disattivando l'autolayout nel builder dell'interfaccia, sia per l'intero storyboard che per il pennino contenente la tua cella. Vedi la risposta qui per i dettagli: http://stackoverflow.com/questions/12833176/indentation-not-working-on-custom-uitableviewcell – jrturton

6

Un'alternativa alla sottoclasse è la revisione dei vincoli in cellForRowAtIndexPath.

Incorpora tutto il contenuto della cella all'interno di una vista contenitore. Quindi, indirizzare i vincoli iniziali e finali a cell.contentView anziché alla cella di visualizzazione tabella.

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

Ecco una sottoclasse, sulla base di altre idee risposte, ho intenzione di basare le mie cellule personalizzati on:

@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

A mio parere, non serve a chiamare initFixedTableViewCell da initWithStyle: reuseIdentifier: poiché si tratta di un bug di Interface Builder che non può verificarsi solo se si carica il cel da un file di pennino. Si spera che nel codice non si debba riprodurre il bug dell'Interfaccia Builder ;-) Inoltre non vedo perché controllare i discendenti nell'elenco dei vincoli. – Adrian

+2

Questo ha funzionato bene per me, ma è necessario preservare anche la priorità del vincolo: "contentViewConstraint.priority = constraint.priority" –

32

Come accennato, Interface Builder di XCode si nasconde il contentView del UITableViewCell. In realtà, tutti gli elementi dell'interfaccia utente aggiunti a UITableViewCell sono in realtà subviews di contentView.

Per il momento, IB non sta facendo la stessa magia per i vincoli di layout, il che significa che sono tutti espressi a livello di UITableViewCell.

Una soluzione è in awakeFromNib di una sottoclasse per spostare tutte NSAutoLayoutConstrains da UITableViewCell alla sua contentView ed esprimerli in termini di 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

Questo ha funzionato per me! Grazie! – JimmyJammed

+0

Questo ha funzionato alla grande! Stava usando una vista tabella raggruppata e dovevo rendere tutto relativo alla cella nel generatore di interfacce anziché a come appariva visivamente, ma ha funzionato per farlo rientrare e ritagliare correttamente! – Gujamin

+0

+1 Aggiunto questo alla mia base 'UITableViewCell' quindi non dovrò mai più occuparmi di questo. – memmons

2

Penso che questo è stato risolto in iOS 7 beta 3 rendendo le soluzioni alternative non necessaria da quel momento in poi (ma probabilmente innocuo come nella maggior parte dei casi diventeranno operazioni vuote).

1

Basato sul codice di Skoota (Sono un principiante, non so molto di quello che hai fatto, ma lavoro eccellente) il mio suggerimento è quello di mettere tutte le tue cose in una vista del contenitore edge-to-edge e aggiungere il seguente:

nel file di intestazione della cella, ho i seguenti: IBOutlets

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

nel file di implementazione, ho quanto segue in 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]; 

Anche in questo caso, il sopra è stato reso possibile da Skoota. Grazie!!! I crediti vanno a lui.

Problemi correlati