Carga de una UITableViewCell reutilizable desde una punta

89

Puedo diseñar UITableViewCells personalizadas y cargarlas sin problemas utilizando la técnica descrita en el hilo que se encuentra en http://forums.macrumors.com/showthread.php?t=545061 . Sin embargo, usar ese método ya no le permite iniciar la celda con un reuseIdentifier, lo que significa que debe crear instancias completamente nuevas de cada celda en cada llamada. ¿Alguien ha descubierto una buena manera de almacenar en caché determinados tipos de celdas para su reutilización, pero aún así poder diseñarlos en Interface Builder?

Greg Martin
fuente

Respuestas:

74

Simplemente implemente un método con la firma de método adecuada:

- (NSString *) reuseIdentifier {
  return @"myIdentifier";
}
Louis Gerbarg
fuente
¿Dónde implementar este método?
Krishnan
2
En su subclase UITableViewCell. developer.apple.com/library/ios/#DOCUMENTATION/UIKit/Reference/…
DenTheMan
5
Esto es arriesgado. ¿Qué sucede si tiene dos subclases de su subclase de celda y usa ambas en una vista de tabla única? Si envían la llamada del identificador de reutilización a super, quitará la cola de una celda del tipo incorrecto ... Creo que debe anular el método reuseIdentifier, pero que devuelva un identificador suplantado cuerda.
SK9
3
Para estar seguro de que es único, puede hacer:return NSStringFromClass([self class]);
ivanzoid
119

En realidad, dado que está construyendo la celda en Interface Builder, simplemente establezca el identificador de reutilización allí:

IB_reuse_identifier

O si está ejecutando Xcode 4, consulte la pestaña del inspector de atributos:

ingrese la descripción de la imagen aquí

(Editar: Después de que XCode genera su XIB, contiene una UIView vacía, pero necesitamos una UITableViewCell; por lo que debe eliminar manualmente la UIView e insertar una celda de vista de tabla. Por supuesto, IB no mostrará ningún parámetro de UITableViewCell para una UIView.)

Tim Keating
fuente
¿Qué configuro los identificadores si utilizo estas celdas creadas por Nib para más de una celda? Esto nos llevará a tener dos celdas con los mismos identificadores.
Krishnan
4
No lo considere un identificador único, considérelo más como un nombre de tipo.
Tim Keating
No veo la opción de establecer un identificador a través del constructor de interfaces incorporado en Xcode 4.3.3. Definitivamente estoy configurando la clase en mi subclase UITableViewCell. ¿Me lo estoy perdiendo o se ha ido?
Tyler
3
Ok, resolví mi problema. Si comienza con un objeto UIView en el generador de interfaces (dentro de Xcode 4) y cambia su clase a UITableViewCell, no obtiene las propiedades específicas de la celda como el identificador de reutilización. Para obtener eso, se supone que debe comenzar con un xib vacío y arrastrar un objeto de celda de tabla, que luego tendrá las propiedades específicas de la celda que puede editar.
Tyler
1
@Krishnan Piénsalo de esta manera: cuando creas la celda de vista de tabla con el identificador X, estás diciendo "Dame una celda del grupo con la etiqueta X". Si la piscina existe y hay una celda libre allí, entonces se la da. De lo contrario, crea el grupo (si es necesario), luego actualiza la celda, la etiqueta con una "X" y luego se la entrega. Por lo tanto, las celdas PUEDEN ser únicas, por ejemplo, podría crear un grupo con una sola celda con un identificador particular, pero la biblioteca usa una estrategia similar a una lista libre para evitar la asignación de memoria / desalojo.
Tim Keating
66

Ahora, en iOS 5 hay un método UITableView apropiado para eso:

- (void)registerNib:(UINib *)nib forCellReuseIdentifier:(NSString *)identifier
wzs
fuente
10
Las otras respuestas en este hilo, incluida la respuesta aceptada, contienen consejos obsoletos.
Kaelin Colclasure
¿Es esto compatible con versiones anteriores? Quiero decir, si desarrollo una aplicación con SDK 5.0 y el objetivo mínimo es 4.0, ¿la aplicación se ejecutará en dispositivos con iOS 4.0, por ejemplo?
Abolfoooud
1
No, esto no es compatible con versiones anteriores, como cualquier API nueva en iOS 5.0.
marzapower
ejemplo trabajado de cómo integrar esto en su controlador: mindfiresolutions.com/…
mblackwell8
47

No recuerdo dónde encontré este código originalmente, pero me ha funcionado muy bien hasta ahora.

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

Ejemplo de configuración de Interface Builder ...

texto alternativo

arcoiris
fuente
12

Mira la respuesta que di a esta pregunta:

¿Es posible diseñar subclases de NSCell en Interface Builder?

No solo es posible diseñar un UITableViewCell en IB, es deseable porque, de lo contrario, todo el cableado manual y la colocación de múltiples elementos es muy tedioso. El rendimiento está bien siempre que tenga cuidado de hacer que todos los elementos sean opacos cuando sea posible. El reuseID se establece en IB para las propiedades de UITableViewCell, luego usa el ID de reutilización coincidente en el código cuando intenta quitar la cola.

También escuché de algunos de los presentadores en la WWDC el año pasado que no debería hacer celdas de vista de tabla en IB, pero es un montón de tonterías.

Kendall Helmstetter Gelner
fuente
2
No debe crear celdas de vista de tabla en IB si necesita transparencia y desea un buen rendimiento de desplazamiento. Para ciertas interfaces de usuario, necesita transparencia (para representar texto sobre gráficos, por ejemplo). A veces, la única forma de lograr un buen desplazamiento en hardware antiguo (pre-A4) es renderizar en código, para evitar que la GPU tenga que componer múltiples capas transparentes.
Nick Forge
2
Es cierto, pero incluso con eso, es mejor que deje que el rendimiento sufra un poco en los dispositivos antiguos para facilitar el mantenimiento de las celdas integradas en IB. También puede usar esta técnica como una celda de plantilla de la que luego dibujará elementos para evitar la composición con un método de dibujo personalizado.
Kendall Helmstetter Gelner
6

Aquí hay otra opción:

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;
    }
}
JDL
fuente
Tenga en cuenta que esta es la única solución publicada hasta ahora que no requiere subclasificar su personalizado UITableViewCellpara establecer un valor único para reuseIdentifer. Creo que esto es lo que estaba buscando la operación original.
charshep
Espero que Apple no niegue mi solicitud por usar esto ... Estoy usando esto para obtener celdas "estáticas" porque en mi vista de tabla, el proceso de llenar una celda es muy lento. De esta manera solo tengo que ejecutarlo una vez (dando un identificador diferente a cada fila). ¡Gracias!
Ricard Pérez del Campo
¡Muy útil ya que no existe un método UITableViewCell para configurar esto manualmente!
Jesse
2

Creo mis celdas de vista personalizadas de manera similar, excepto que conecto la celda a través de un IBOutlet.

los [nib objectAt...] enfoque es susceptible de cambios en las posiciones de los elementos de la matriz.

los UIViewController enfoque es bueno, solo lo probé y funciona lo suficientemente bien.

PERO...

En todos los casos initWithStyle NO se llama constructor, por lo que no se realiza una inicialización predeterminada.

He leído varios lugares sobre el uso initWithCoderoawakeFromNib , pero no hay evidencia concluyente de que cualquiera de estos sea la forma correcta.

Aparte de llamar explícitamente a algún método de inicialización en el cellForRowAtIndexPathmétodo, todavía no he encontrado una respuesta a esto.

sww
fuente
awakeFromNib es la forma correcta de reaccionar ante la carga de un objeto desde un NIB.
Jon Hess
2

Hace un tiempo encontré una excelente publicación de blog sobre este tema en blog.atebits.com , y desde entonces comencé a usar la clase de Loren Brichter ABTableViewCell para hacer todas mis UITableViewCells.

Terminas con un contenedor UIView simple para colocar todos tus widgets, y el desplazamiento es muy rápido.

Espero que esto sea de utilidad.

Eric
fuente
2

Esta técnica también funciona y no requiere un ivar funky en su controlador de vista para administrar la memoria. Aquí, la celda de vista de tabla personalizada vive en un xib llamado "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];
  }
Bill Garrison
fuente
1
Archivar y desarchivar es totalmente innecesario.
Bryan Henry
1
No es necesario archivar / desarchivar si está de acuerdo con cargar la celda desde su plumilla siempre que no se pueda quitar de la cola. Sin embargo, si solo desea cargar la celda desde su plumilla exactamente una vez , debe almacenarla en la memoria caché. Logro ese almacenamiento en caché usando NSKeyedArchiving porque UITableViewCell no implementa NSCopying.
Bill Garrison
1
Dicho todo esto, usar UINib para cargar la celda logra el mismo efecto: cargar desde el disco una vez, cargar desde la memoria a partir de entonces.
Bill Garrison
2

El método Louis funcionó para mí. Este es el código que utilizo para crear UITableViewCell desde la punta:

- (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;
}
ggarber
fuente
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;
}
Mohit
fuente
1

La solución gustavogb no funciona para mí, lo que probé es:

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

Parece funcionar. BlogTableViewCell es el IBOutlet de la celda y ChainesController es el propietario del archivo.

groumpf
fuente
1

De los documentos de UITableView sobre dequeueWithReuseIdentifier : "Una cadena que identifica el objeto de celda que se reutilizará. Por defecto, el identificador de una celda reutilizable es su nombre de clase, pero puede cambiarlo a cualquier valor arbitrario".

Anular -reuseIdentifer usted mismo es riesgoso. ¿Qué sucede si tiene dos subclases de su subclase de celda y usa ambas en una vista de tabla única? Si envían la llamada del identificador de reutilización a super, quitará de la cola una celda del tipo incorrecto ... Creo que debe anular el método reuseIdentifier, pero que devuelva un identificador suplantado cuerda. O, si no se ha especificado uno, haga que devuelva la clase como una cadena.

SK9
fuente
0

Por lo que vale, le pregunté a un ingeniero de iPhone sobre esto en una de las charlas técnicas de iPhone. Su respuesta fue: "Sí, es posible usar IB para crear células. Pero no lo hagas. Por favor, no lo hagas".

agosto
fuente
1
Eso es extraño. Al menos dos de las charlas en la charla de Nueva York tenían un código de demostración que usaba celdas creadas en IB.
Shawn Craver
3
No estoy seguro de creer esto ya que usan IB para crear celdas en el proyecto de ejemplo de Celdas de vista de tabla avanzada de Apple.
Fui robado
Gracias por eso. Cada vez que lo he hecho me he encontrado con problemas. Esta podría ser la razón
skorulis
Probablemente no tenía suficiente conocimiento al respecto.
aryaxt
0

Seguí las instrucciones de Apple enlazadas por Ben Mosher (¡gracias!) Pero descubrí que Apple omitió un punto importante. El objeto que diseñan en IB es solo una UITableViewCell, al igual que la variable que cargan desde ella. Pero si realmente lo configura como una subclase personalizada de UITableViewCell y escribe los archivos de código para la subclase, puede escribir declaraciones IBOutlet y métodos IBAction en el código y conectarlos a sus elementos personalizados en IB. Entonces no es necesario usar etiquetas de vista para acceder a estos elementos y puede crear cualquier tipo de celda loca que desee. Es el paraíso de Cocoa Touch.

David Casseres
fuente