2011-10-05 17 views
11

Ho un UITableViewCell personalizzato che visualizza vari attributi di un oggetto Person (supportato da Core Data) ... alcune etichette, immagini ecc. Attualmente imposto l'intera tableview per ricaricarla ogni volta che cambiano le proprietà, e ovviamente non è efficiente. So che con KVO, dovrei essere in grado di aggiungere un listener a un'etichetta nella cella in grado di ascoltare le modifiche nelle proprietà della persona. Ma non sono sicuro di come implementarlo e non riesco a trovare alcun esempio.aggiungendo KVO a UITableViewCell

Ecco quello che di solito faccio in cellForRowAtIndexPath di mio UITableView:

- (UITableViewCell *) tableView: (UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath 
    { 
     static NSString *simple = @"CustomCellId"; 

     CustomCell *cell = (CustomCell *) [tableView dequeueReusableCellWithIdentifier:simple]; 

     if (cell == nil) 
     { 
      NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@"CustomCell" owner:self options:nil]; 

      for (id findCell in nib) 
      { 
       if ([findCell isKindOfClass: [CustomCell class]]) 
       { 
        cell = findCell; 
       }  
      } 
     } 
     Person *managedObject = [self.someArray objectAtIndex: indexPath.row]; 
     cell.namelabel.text = managedObject.displayName; 
     return cell; 
} 

La cella è collegato in IB. Vorrei rilevare quando cambia displayName e aggiornare solo l'etichetta del nome. Grazie

risposta

5

Per lo sfondo, è probabile che si desideri leggere le guide di rilevamento valore-chiave e valore-chiave, se non lo si è già fatto. Quindi rivedere i metodi della categoria NSKeyValueObserving.

http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Protocols/NSKeyValueObserving_Protocol/Reference/Reference.html

In poche parole, è necessario gestire con attenzione aggiungere e rimuovere l 'oggetto osservando all'elenco oggetti osservati degli osservatori (scusate la ridondanza apparente di questa affermazione). Non vuoi avere un oggetto che va via con gli osservatori ancora registrati, o ricevi lamentele e possibili altri problemi.

Detto questo, si utilizza -addObserver:keyPath:options:context per aggiungere un oggetto come osservatore. Il contesto dovrebbe essere una stringa dichiarata staticamente. L'argomento delle opzioni controlla quali dati vengono restituiti nel tuo metodo di osservazione (vedi sotto). KeyPath è il percorso dei nomi di proprietà dall'oggetto osservato alla proprietà osservata (questo può attraversare più oggetti e verrà aggiornato quando gli oggetti intermedi cambiano, non solo quando la proprietà foglia cambia).

Nel tuo caso, è possibile osservare l'etichetta e utilizzare il keyPath text o la cella e utilizzare il percorso chiave nameLabel.text. Se la classe di visualizzazione tabella è stata progettata in modo diverso, è possibile osservare l'intero array di celle, ma su UITableView non esiste tale proprietà. Il problema con l'osservazione della cella è che la vista tabella potrebbe eliminarlo in qualsiasi momento (se il progetto utilizza più celle che hanno lo stesso scopo in un elenco di lunghezza variabile). Se sai che le tue cellule sono statiche, puoi probabilmente osservarle senza preoccupazioni.

Una volta che hai osservatore registrati, che osservatore deve implementare -observeValueForKeyPath:ofObject:change:context:, confermano che il contesto corrisponde (basta confrontare il valore del puntatore per l'indirizzo del tuo stringa statica, altrimenti invocare la realizzazione di super), quindi esaminare il dizionario cambiamento per il dati che desideri (o semplicemente chiedi direttamente l'oggetto) e utilizzali per aggiornare il tuo modello come meglio credi.

Esistono molti esempi di codice KVO nel codice di esempio, incluso nel sito degli sviluppatori Apple e come parte dei campioni dei collegamenti sul sito Malcolm Crawford (mmalc), ma la maggior parte di essi è per Mac OS X, non per iOS.

+0

Pensa di aver capito, grazie al tuo aiuto. Inserito codice di seguito, ma ti darà credito con la risposta. –

+0

È un errore osservare qualsiasi percorso chiave non esplicitamente documentato come conforme a KVO. Non è possibile osservare il 'testo' di un'etichetta. –

+0

Se non sbaglio, non si deve osservare la 'cella', ma l'oggetto' Persona'! Perché l'oggetto potrebbe cambiare e la cella dovrebbe rispondere! –

2

Questo funziona:

In configureCell:

[managedObject addObserver: cell forKeyPath: @"displayName" options:NSKeyValueObservingOptionNew context: @"Context"]; 

In CustomCell:

- (void)observeValueForKeyPath:(NSString *)keyPath 
         ofObject:(id)object 
         change:(NSDictionary *)change 
         context:(void *)context 
{ 
    Person *label = (Person *) object; 
    self.namelabel.text = [label valueForKey:@"displayName"]; 
} 
+8

Quando si rimuove l'osservatore e per quanto riguarda il riutilizzo della cella? – Besi

+0

Nel mio caso, non volevo rimuovere gli osservatori (dal momento che sto aggiungendo l'osservatore all'oggetto gestito, suppongo che dovrebbe essere ogni volta che viene eliminato o messo in errore?). Per il riutilizzo delle celle, potresti voler controllare se ([managedObject observationInfo]! = Nil) prima di aggiungere l'osservatore.Ho effettivamente rielaborato il mio codice dopo aver postato questo, quindi non ho finito per usare questa soluzione; quindi non ho lavorato a tutti questi dettagli. –

18

La risposta di cui sopra è grande per celle statiche. L'utilizzo di KVO per UITableViewCell s funziona ancora con il riutilizzo delle celle.Aggiungi gli osservatori di cui hai bisogno quando la cella sta per apparire e rimuovili quando la cella non viene più visualizzata. L'unico accorgimento è che Apple sembra essere in contrasto circa l'invio di didEndDisplayingCell :, così gli osservatori devono essere rimossi in due punti su iOS 6,1

@implementation MyTableViewCell 

@property MyTableViewController * __weak parentTVC; 

- (UITableViewCell *)tableView:(UITableView *)tableView 
cellForRowAtIndexPath:(NSIndexPath *)indexPath 
{ 
    ((MyTableViewCell *)cell).parentTVC = self; 
    // Don't add observers, or the app may crash later when cells are recycled 
} 


- (void)tableView:(UITableView *)tableView 
    willDisplayCell:(HKTimelineCell *)cell 
forRowAtIndexPath:(NSIndexPath *)indexPath 
{ 
    // Add observers 
} 

- (void)tableView:(UITableView *)tableView 
didEndDisplayingCell:(UITableViewCell *)cell 
forRowAtIndexPath:(NSIndexPath *)indexPath 
{ 
    [self removeMyKVOObservers]; 
} 

- (void)viewWillDisappear:(BOOL)animated 
{ 
    for (MyTableViewCell *cell in self.visibleCells) { 
     // note! didEndDisplayingCell: isn't sent when the entire controller is going away! 
     [self removeMyKVOObservers]; 
    } 
} 

Quanto segue può verificarsi se gli osservatori non sono puliti. L'osservatore potrebbe provare a notificare qualsiasi oggetto si trovi in ​​quella posizione di memoria, che potrebbe anche non esistere.

<NSKeyValueObservationInfo 0x1d6e4860> ( <NSKeyValueObservance 0x1d4ea9f0: Observer: 0x1d6c9540, Key path: someKeyPath, Options: <New: YES, Old: NO, Prior: NO> Context: 0x0, Property: 0x1c5c7e60> <NSKeyValueObservance 0x1d1bff10: Observer: 0x1d6c9540, Key path: someOtherKeyPath, Options: <New: YES, Old: NO, Prior: NO> Context: 0x0, Property: 0x1c588290>)

+0

Funziona solo per iOS 6.0+. Qualcuno ha una soluzione per iOS 5.1+? –

+0

molto completo, perfetto! – djibouti33

+0

spiegazione chiara ... grazie –

1

Nel mio caso ho aggiunto un osservatore per l'etichetta della cella personalizzato forKeyPath "testo" con le opzioni (NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld).

Quando si osserva il valore per il percorso chiave Posso controllare per garantire il percorso chiave è quello che voglio, tanto per fare un misura in più ed allora io chiamo il mio metodo per che cosa mai operazione che voglio svolgere per quell'etichetta

ad esempio nel mio caso

-(id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier 
{ 
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; 

    if (self) { 
     // Helpers 
     CGSize cellSize = self.contentView.frame.size; 

     CGRect sizerFrame = CGRectZero; 
     sizerFrame.origin.x = kDefaultUITableViewCellContentLeftInset; 
     sizerFrame.origin.y = kDefaultUITableViewCellContentTopInset; 

     // The Profile Image 
     CGRect imageFrame = CGRectMake(sizerFrame.origin.x, sizerFrame.origin.y, kDefaultProfilePictureSizeBWidth, kDefaultProfilePictureSizeBHeight); 
     self.userProfilePictureUIImageView = [[UIImageView alloc] initWithFrame:imageFrame]; 
     [self.userProfilePictureUIImageView setImage:[UIImage imageNamed:@"placeholderImage"]]; 
     [ApplicationUtilities formatViewLayer:self.userProfilePictureUIImageView withBorderRadius:4.0]; 

     // adjust the image content mode based on the lenght of it's sides 
     CGSize avatarSize = self.userProfilePictureUIImageView.image.size; 

     if (avatarSize.width < avatarSize.height) { 
      [self.userProfilePictureUIImageView setContentMode:UIViewContentModeScaleAspectFill]; 
     } else { 
      [self.userProfilePictureUIImageView setContentMode:UIViewContentModeScaleAspectFit]; 
     } 

     CGFloat readStateSize = 10.0; 
     CGRect readStateFrame = CGRectMake((imageFrame.origin.x + imageFrame.size.width) - readStateSize, CGRectGetMaxY(imageFrame) + 4, readStateSize, readStateSize); 

     // Read State 
     self.readStateUIImageView = [[UIImageView alloc] initWithFrame:readStateFrame]; 
     self.readStateUIImageView.backgroundColor = RGBA2UIColor(0.0, 157.0, 255.0, 1.0); 
     [ApplicationUtilities formatViewLayer:self.readStateUIImageView withBorderRadius:readStateSize/2]; 


     sizerFrame.origin.x = CGRectGetMaxX(imageFrame) + kDefaultViewContentHorizontalSpacing; 
     // read just the width of the senders label based on the width of the message label 
     CGRect messageLabelFrame = sizerFrame; 
     messageLabelFrame.size.width = cellSize.width - (CGRectGetMinX(messageLabelFrame) + kDefaultViewContentHorizontalSpacing); 
     messageLabelFrame.size.height = kDefaultInitialUILabelHeight; 

     // Store the original frame for resizing 
     initialLabelFrame = messageLabelFrame; 

     self.messageLabel = [[UILabel alloc]initWithFrame:messageLabelFrame]; 
     [self.messageLabel setBackgroundColor:[UIColor clearColor]]; 
     [self.messageLabel setFont:[UIFont systemFontOfSize:14.0]]; 
     [self.messageLabel setTextColor:[UIColor blackColor]]; 
     [self.messageLabel setNumberOfLines:2]; 
     [self.messageLabel setText:@""]; 

     // Modify Sizer Frame for Message Date Label 
     sizerFrame = initialLabelFrame; 
     // Modify the y offset 
     sizerFrame.origin.y = CGRectGetMaxY(sizerFrame) + kDefaultViewContentVerticalSpacing; 

     // Message Date 
     self.messageDateLabel = [[UILabel alloc] initWithFrame:CGRectZero]; 
     [self.messageDateLabel setBackgroundColor:[UIColor clearColor]]; 
     [self.messageDateLabel setFont:[UIFont systemFontOfSize:12.0]]; 
     [self.messageDateLabel setTextColor:RGBA2UIColor(200.0, 200.0, 200.0, 1.0)]; 
     [self.messageDateLabel setHighlightedTextColor:[UIColor whiteColor]]; 
     [self.messageDateLabel setTextAlignment:NSTextAlignmentRight]; 
     [self.messageDateLabel setNumberOfLines:1]; 
     [self.messageDateLabel setText:@"Message Date"]; 
     [self.messageDateLabel sizeToFit]; 

     [self.contentView addSubview:self.userProfilePictureUIImageView]; 
     [self.contentView addSubview:self.readStateUIImageView]; 
     [self.contentView addSubview:self.messageDateLabel]; 
     [self.contentView addSubview:self.messageLabel]; 

     // Add KVO for all text labels 
     [self.messageDateLabel addObserver:self forKeyPath:@"text" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:NULL]; 
     [self.messageLabel addObserver:self forKeyPath:@"text" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:NULL]; 

    } 
    return self; 
} 

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 
{ 
    if ([keyPath isEqual:@"text"]) { 

     [self resizeCellObjects]; 
    } 
} 

-(void)resizeCellObjects 
{ 
    // Resize and reposition the message label 
    CGRect messageLabelFrame = initialLabelFrame; 

    self.messageLabel.frame = messageLabelFrame; 
    [self.messageLabel setNumberOfLines:2]; 
    [self.messageLabel sizeToFit]; 

    // Resize the messageDate label 
    CGRect messageDateFrame = initialLabelFrame; 
    messageDateFrame.origin.y = CGRectGetMaxY(self.messageLabel.frame) + kDefaultViewContentVerticalSpacing; 
    self.messageDateLabel.frame = messageDateFrame; 

    [self.messageDateLabel sizeToFit]; 

} 
0

preferisco una soluzione in cui l'UITableViewCell fa tutto il KVO da solo. La mia configurazione è la seguente:

Nella sottoclasse della mia cella, ho una proprietà che mantiene un forte riferimento alla mia classe del modello da cui recupero i miei dati, e un metodo che chiamo quando voglio allegare un nuovo oggetto alla proprietà:

@interface MyTableViewCell : UITableViewCell 

@property (atomic) id object; 
- (void)populateFromObject:(id)object; 

Implementazione:

- (void)awakeFromNib { 
[super awakeFromNib]; 
self.contentView.hidden = YES;// avoid displaying an unpopulated cell 
} 

- (void)populateFromObject:(id)object { 
    dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0),^{// handle KVO on a bg thread 
     if (object && (self.object != object)) {// if new object differs from property... 
      [self unregisterFromKVO];// ...unregister from old object and... 
      self.object = object; 
      for (NSString *keyToObserve in [[object class] displayKeys]) {// ...register to new object 
       [object addObserver:self forKeyPath:keyToObserve options:0 context:nil]; 
      } 
     } 
    }); 
    dispatch_async(dispatch_get_main_queue(), ^{// UI updates on main thread only 
     // update your outlets here 
     self.contentView.hidden  = NO;// finally display the cell now that it is properly populated 
    }); 
} 



// =========== 
#pragma mark - KVO 
// =========== 

// KVO notification 
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context { 
    [self populateFromObject:object]; 
} 

- (void)unregisterFromKVO { 
     for (NSString *keyToObserve in [YourModelObject displayKeys]) { 
      [self.object removeObserver:self forKeyPath:keyToObserve]; 
     } 
} 

- (void)dealloc { 
    [self unregisterFromKVO]; 
} 

si noti che l'attuale KVO è gestito su un thread in background per evitare di bloccare il thread principale durante lo scorrimento. Si noti inoltre che restituisce immediatamente e pertanto visualizzerà una cella non popolata. Per evitare ciò, nascondiamo la vista del contenuto fino a quando la cella non è completamente popolata. Ora l'unica cosa che resta da implementare è un metodo di classe su YourModelObject che restituisce un array di chiavi che si desidera KVO:

+ (NSArray<NSString *> *)displayKeys { 
    return @[@"name",@"Street", @"ZipCode"]; 
} 

..e in UITableViewController:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 
MyTableViewCell *cell  = [tableView dequeueReusableCellWithIdentifier:@"reuseid" forIndexPath:indexPath]; 
YourModelObject *obj = [myModelArray objectAtIndex:indexPath.row]; 
[cell populateFromObject:obj]; 

return cell; 
} 

Il riferimento forte da la cella dell'oggetto modello assicura che l'oggetto non venga deallocato mentre la cella osserva ancora una delle sue proprietà, cioè è visibile. Una volta che la cellula è stata deallocata, la KVO non è registrata e solo allora l'oggetto del modello sarà deallocato. Per comodità, ho anche un debole riferimento dall'oggetto modello alla cella che può tornare utile quando si implementano i metodi delegati UITableView.

+0

Il -populateFromObject: metodo invia due attività chiamando due volte dispatch_async(). Le due attività vengono inviate a code diverse. Se non sbaglio, non c'è alcuna garanzia che la seconda attività verrà eseguita dopo che è stata completata la prima attività, quindi l'affermazione che "nascondiamo la vista del contenuto finché la cella non è completamente popolata" non è abbastanza precisa. –