Caricamento di un UITableViewCell riutilizzabile da un pennino


89

Sono in grado di progettare UITableViewCells personalizzati e caricarli correttamente utilizzando la tecnica descritta nel thread trovato su http://forums.macrumors.com/showthread.php?t=545061 . Tuttavia, l'utilizzo di quel metodo non ti consente più di inizializzare la cella con un reuseIdentifier, il che significa che devi creare istanze completamente nuove di ogni cella ad ogni chiamata. Qualcuno ha trovato un buon modo per memorizzare nella cache determinati tipi di celle per il riutilizzo, ma essere comunque in grado di progettarli in Interface Builder?

Risposte:


74

Basta implementare un metodo con la firma del metodo appropriata:

- (NSString *) reuseIdentifier {
  return @"myIdentifier";
}

Dove implementare questo metodo?
Krishnan


5
Questo è rischioso. Cosa succede se hai due sottoclassi della tua sottoclasse di celle e le utilizzi entrambe in un'unica vista tabella? Se inviano la chiamata all'identificatore di riutilizzo su super, rimuoverai dalla coda una cella del tipo sbagliato .............. Penso che sia necessario sovrascrivere il metodo reuseIdentifier, ma deve restituire un identificatore soppiantato corda.
SK9

3
Per essere sicuro che sia unico, puoi fare:return NSStringFromClass([self class]);
ivanzoid

119

In realtà, dato che stai costruendo la cella in Interface Builder, imposta l'identificatore di riutilizzo lì:

IB_reuse_identifier

Oppure, se stai utilizzando Xcode 4, controlla la scheda Inspector Attributi:

inserisci qui la descrizione dell'immagine

(Modifica: dopo che il tuo XIB è stato generato da XCode, contiene un UIView vuoto, ma abbiamo bisogno di un UITableViewCell; quindi devi rimuovere manualmente UIView e inserire una cella di visualizzazione tabella. Ovviamente, IB non mostrerà alcun parametro UITableViewCell per un UIView.)


Cosa imposto gli identificatori, se utilizzo questo pennino creato per più celle? Questo ci porterà ad avere due celle con gli stessi identificatori.
Krishnan

4
Non pensarlo come un identificatore univoco: pensalo più come un nome di tipo.
Tim Keating

Non vedo l'opzione per impostare un identificatore tramite il generatore di interfacce integrato in Xcode 4.3.3. Sto sicuramente impostando la classe sulla mia sottoclasse UITableViewCell. Mi manca solo o è sparito?
Tyler

3
Ok, ho risolto il mio problema. Se inizi con un oggetto UIView in Interface Builder (all'interno di Xcode 4) e cambi la sua classe in UITableViewCell, non ottieni le proprietà specifiche della cella come l'identificatore di riutilizzo. Per ottenerlo, dovresti iniziare con un xib vuoto e trascinare in un oggetto cella di tabella, che avrà quindi le proprietà specifiche della cella che puoi modificare.
Tyler

1
@Krishnan Pensala in questo modo: quando crei la cella della vista tabella con l'identificatore X, stai dicendo "Dammi una cella dal pool etichettato X". Se la piscina esiste e c'è una cella libera, allora te la dà. Altrimenti, crea il pool (se necessario), quindi aggiorna la cella, la etichetta "X" e poi te la passa. Quindi le celle POSSONO essere uniche - ad esempio potresti creare un pool con una sola cella con un identificatore particolare - ma la libreria utilizza una strategia simile a una lista libera per evitare l'allocazione / deallocazione della memoria.
Tim Keating

66

Ora, in iOS 5 esiste un metodo UITableView appropriato per questo:

- (void)registerNib:(UINib *)nib forCellReuseIdentifier:(NSString *)identifier

10
Le altre risposte in questo thread, inclusa la risposta accettata, contengono consigli obsoleti.
Kaelin Colclasure

è compatibile con le versioni precedenti? Voglio dire, se sviluppo un'app con SDK 5.0 e target minimo 4.0, l'app verrà eseguita su dispositivi con iOS 4.0, ad esempio?
Abolfoooud

1
No, non è compatibile con le versioni precedenti, come qualsiasi nuova API in iOS 5.0.
marzapower

esempio funzionante di come integrarlo nel controller: mindfiresolutions.com/…
mblackwell8

47

Non ricordo dove ho trovato originariamente questo codice, ma finora ha funzionato benissimo per me.

- (UITableViewCell *)tableView:(UITableView *)tableView 
         cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    static NSString *CellIdentifier = @"CustomTableCell";
    static NSString *CellNib = @"CustomTableCellView";

    UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        NSArray *nib = [[NSBundle mainBundle] loadNibNamed:CellNib owner:self options:nil];
        cell = (UITableViewCell *)[nib objectAtIndex:0];
    }

    // perform additional custom work...

    return cell;
}

Esempio di configurazione di Interface Builder ...

testo alternativo


12

Guarda la risposta che ho dato a questa domanda:

È possibile progettare sottoclassi NSCell in Interface Builder?

Non solo è possibile progettare un UITableViewCell in IB, è auspicabile perché altrimenti tutto il cablaggio manuale e il posizionamento di più elementi è molto noioso. La performance va bene fintanto che stai attento a rendere tutti gli elementi opachi quando possibile. Il reuseID è impostato in IB per le proprietà di UITableViewCell, quindi si utilizza l'ID di riutilizzo corrispondente nel codice quando si tenta di rimuovere dalla coda.

Ho anche sentito da alcuni dei relatori al WWDC l'anno scorso che non dovresti creare celle di visualizzazione tabella in IB, ma è un carico di cuccette.


2
Non dovresti creare celle di visualizzazione tabella in IB se hai bisogno di trasparenza e desideri buone prestazioni di scorrimento. Per alcune interfacce utente, è necessaria la trasparenza (per eseguire il rendering del testo sulla grafica, ad esempio). A volte l'unico modo per ottenere un buon scorrimento su hardware precedente (pre-A4) è eseguire il rendering in codice, per evitare che la GPU debba comporre più livelli trasparenti.
Nick Forge

2
Vero, ma anche con questo potresti essere meglio lasciare che le prestazioni soffrano leggermente sui vecchi dispositivi per la più facile manutenibilità delle celle costruite in IB. È inoltre possibile utilizzare questa tecnica come cella modello da cui disegnare gli elementi per evitare la composizione con un metodo di disegno personalizzato.
Kendall Helmstetter Gelner


6

Ecco un'altra opzione:

NSString * cellId = @"reuseCell";  
//...
NSArray * nibObjects = [[NSBundle mainBundle] loadNibNamed:@"CustomTableCell" owner:nil options:nil];

for (id obj in nibObjects)
{
    if ([obj isKindOfClass:[CustomTableCell class]])
    {
        cell = obj;
        [cell setValue:cellId forKey:@"reuseIdentifier"];
        break;
    }
}

Tieni presente che questa è l'unica soluzione pubblicata finora che non richiede la UITableViewCellcreazione di sottoclassi della tua personalizzazione per impostare un valore univoco per reuseIdentifer. Penso che questo fosse ciò che l'operazione originale stava effettivamente cercando.
charshep

Spero che Apple non neghi la mia applicazione per l'utilizzo di questo ... Lo sto usando per ottenere celle "statiche" perché nella mia visualizzazione tabella, il processo di riempimento di una cella è piuttosto lento. In questo modo devo solo eseguirlo una volta (assegnando un identificatore diverso a ciascuna riga). Grazie!
Ricard Pérez del Campo

Molto utile in quanto non esiste un metodo UITableViewCell per impostarlo manualmente!
Jesse

2

Creo le mie celle di visualizzazione personalizzate in modo simile, tranne per il fatto che collego la cella tramite un IBOutlet.

L' [nib objectAt...]approccio è suscettibile di modifiche alle posizioni degli elementi nella matrice.

L' UIViewControllerapproccio è buono: l'ho appena provato e funziona abbastanza bene.

MA...

In tutti i casi il initWithStylecostruttore NON viene chiamato, quindi non viene eseguita alcuna inizializzazione predefinita.

Ho letto vari punti sull'utilizzo di initWithCodero awakeFromNib, ma nessuna prova conclusiva che uno di questi sia il modo giusto.

Oltre a chiamare esplicitamente un metodo di inizializzazione nel cellForRowAtIndexPathmetodo, non ho ancora trovato una risposta a questo.


awakeFromNib è il modo giusto per reagire a un oggetto caricato da un NIB.
Jon Hess

2

Qualche tempo fa ho trovato un ottimo post sul blog su questo argomento su blog.atebits.com e da allora ho iniziato a utilizzare la classe ABTableViewCell di Loren Brichter per eseguire tutte le mie UITableViewCell.

Ti ritroverai con un semplice contenitore UIView per mettere tutti i tuoi widget e lo scorrimento è velocissimo.

Spero che questo sia utile.


2

Questa tecnica funziona anche e non richiede un ivar funky nel controller della vista per la gestione della memoria. Qui, la cella della vista tabella personalizzata risiede in un xib denominato "CustomCell.xib".

 static NSData *sLoadedCustomCell = nil;

 cell = [tableView dequeueReusableCellWithIdentifier:@"CustomCell"];
 if (cell == nil) 
 {
   if (sLoadedCustomCell == nil) 
   {        
      // Load the custom table cell xib
      // and extract a reference to the cell object returned
      // and cache it in a static to avoid reloading the nib again.

      for (id loadedObject in [[NSBundle mainBundle] loadNibNamed:@"CustomCell" owner:nil options:nil]) 
      {
        if ([loadedObject isKindOfClass:[UITableViewCell class]]) 
        {
          sLoadedCustomCell = [[NSKeyedArchiver archivedDataWithRootObject: loadedObject] retain];
          break;
        }
    }
    cell = (UITableViewCell *)[NSKeyedUnarchiver unarchiveObjectWithData: sLoadedCustomCell];
  }

1
L'archiviazione e l'annullamento dell'archiviazione sono del tutto inutili.
Bryan Henry

1
Archiviare / annullare l'archiviazione non è necessario se si sta bene caricando la cella dal suo pennino ogni volta che non può essere rimossa dalla coda. Tuttavia, se desideri caricare la cella dal suo pennino esattamente una volta , devi memorizzarla nella cache. Realizzo questa memorizzazione nella cache utilizzando NSKeyedArchiving perché UITableViewCell non implementa NSCopying.
Bill Garrison

1
Detto questo, usando UINib per caricare la cella si ottiene lo stesso effetto: carica una volta dal disco, poi carica dalla memoria.
Bill Garrison

2

Il metodo Louis ha funzionato per me. Questo è il codice che utilizzo per creare UITableViewCell dal pennino:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{   
    UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:@"CustomCellId"];

    if (cell == nil) 
    {
        UIViewController *c = [[UIViewController alloc] initWithNibName:@"CustomCell" bundle:nil];
        cell = (PostCell *)c.view;
        [c release];
    }

    return cell;
}

2
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

static NSString *simpleTableIdentifier = @"CustomCell";

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

    [cell setSelectionStyle:UITableViewCellSelectionStyleNone];
}         

return cell;
}

1

La soluzione gustavogb non funziona per me, quello che ho provato è:

ChainesController *c = [[ChainesController alloc] initWithNibName:@"ChainesController" bundle:nil];
[[NSBundle mainBundle] loadNibNamed:@"ChaineArticleCell" owner:c options:nil];
cell = [c.blogTableViewCell retain];
[c release];

Sembra funzionare. BlogTableViewCell è l'IBOutlet per la cella e ChainesController è il proprietario del file.


1

Dai documenti di UITableView riguardanti dequeueWithReuseIdentifier: "Una stringa che identifica l'oggetto cella da riutilizzare. Per impostazione predefinita, l'identificatore di una cella riutilizzabile è il nome della sua classe, ma puoi cambiarlo con qualsiasi valore arbitrario."

Ignorare -reuseIdentifer è rischioso. Cosa succede se hai due sottoclassi della tua sottoclasse di celle e le utilizzi entrambe in un'unica vista tabella? Se inviano la chiamata all'identificatore di riutilizzo su super, rimuoverai dalla coda una cella del tipo sbagliato .............. Penso che sia necessario sovrascrivere il metodo reuseIdentifier, ma deve restituire un identificatore soppiantato corda. Oppure, se non ne è stato specificato uno, fare in modo che restituisca la classe come stringa.


0

Per quello che vale, ho chiesto informazioni a un ingegnere di iPhone in uno degli iPhone Tech Talks. La sua risposta è stata: "Sì, è possibile usare l'IB per creare cellule. Ma non farlo. Per favore, non farlo".


1
È strano. Almeno due dei discorsi al discorso di New York avevano un codice demo che utilizzava celle create in IB.
Shawn Craver,

3
Non sono sicuro di crederci poiché usano IB per creare celle nel progetto di esempio di celle di visualizzazione tabella avanzata di Apple.
rubato

Grazie per quello. Ogni volta che l'ho fatto ho incontrato problemi. Questo potrebbe essere il motivo
skorulis

Probabilmente non ne sapeva abbastanza.
aryaxt

0

Ho seguito le istruzioni di Apple come linkato da Ben Mosher (grazie!) Ma ho scoperto che Apple ha omesso un punto importante. L'oggetto che progettano in IB è solo un UITableViewCell, così come la variabile che caricano da esso. Ma se lo imposti effettivamente come una sottoclasse personalizzata di UITableViewCell e scrivi i file di codice per la sottoclasse, puoi scrivere dichiarazioni IBOutlet e metodi IBAction nel codice e collegarli ai tuoi elementi personalizzati in IB. Quindi non è necessario utilizzare i tag di visualizzazione per accedere a questi elementi e puoi creare qualsiasi tipo di cella pazza che desideri. È il paradiso Cocoa Touch.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.