En un guión gráfico, ¿cómo hago una celda personalizada para usar con múltiples controladores?

216

Estoy tratando de usar guiones gráficos en una aplicación en la que estoy trabajando. En la aplicación hay Listas y Usuarios y cada uno contiene una colección del otro (miembros de una lista, listas propiedad de un usuario). Entonces, en consecuencia, tengo ListCelly UserCellclases. El objetivo es hacer que sean reutilizables en toda la aplicación (es decir, en cualquiera de mis controladores de vista de tabla).

Ahí es donde me encuentro con un problema.

¿Cómo creo una celda de vista de tabla personalizada en el guión gráfico que se puede reutilizar en cualquier controlador de vista?

Aquí están las cosas específicas que he probado hasta ahora.

  • En el Controlador n. ° 1, agregué una celda prototipo, configuré la clase en mi UITableViewCellsubclase, configuré el ID de reutilización, agregué las etiquetas y las conecté a las salidas de la clase. En el Controlador n. ° 2, agregó una celda prototipo vacía, configúrela en la misma clase y reutilice la identificación como antes. Cuando se ejecuta, las etiquetas nunca aparecen cuando las celdas se muestran en el Controlador # 2. Funciona bien en el controlador # 1.

  • Diseñó cada tipo de celda en un NIB diferente y lo conectó a la clase de celda apropiada. En storyboard, agregué una celda prototipo vacía y configuré su clase y reutilicé la identificación para referirme a mi clase de celda. En los viewDidLoadmétodos de los controladores , se registraron esos archivos NIB para la identificación de reutilización. Cuando se muestra, las células en ambos controladores estaban vacías como el prototipo.

  • Los prototipos mantenidos en ambos controladores vacían y configuran la clase y reutilizan la identificación en mi clase de celda. Construyó la interfaz de usuario de las celdas completamente en código. Las células funcionan perfectamente en todos los controladores.

En el segundo caso, sospecho que el prototipo siempre está anulando el NIB y si mato las células prototipo, el registro de mi NIB para la identificación de reutilización funcionaría. Pero entonces no sería capaz de configurar segues de las celdas a otros marcos, que es realmente el objetivo de usar guiones gráficos.

Al final del día, quiero dos cosas: conectar los flujos basados ​​en la vista de tabla en el guión gráfico y definir diseños de celdas visualmente en lugar de en código. No puedo ver cómo llegar a ambos hasta ahora.

Acantilado, w
fuente

Respuestas:

205

Según tengo entendido, quieres:

  1. Diseñe una celda en IB que se pueda usar en varias escenas del guión gráfico.
  2. Configure segmentos únicos del guión gráfico desde esa celda, dependiendo de la escena en la que se encuentre la celda.

Desafortunadamente, actualmente no hay forma de hacer esto. Para comprender por qué sus intentos anteriores no funcionaron, debe comprender más sobre cómo funcionan los guiones gráficos y las celdas de vista de tabla prototipo. (Si no le importa por qué estos otros intentos no funcionaron, siéntase libre de irse ahora. No tengo soluciones mágicas para usted, aparte de sugerirle que presente un error).

Un guión gráfico no es, en esencia, mucho más que una colección de archivos .xib. Cuando carga un controlador de vista de tabla que tiene algunas celdas prototipo fuera de un guión gráfico, esto es lo que sucede:

  • Cada celda prototipo es en realidad su propia mini punta incrustada. Entonces, cuando el controlador de vista de tabla se está cargando, se ejecuta a través de cada punta y llamada de la celda prototipo -[UITableView registerNib:forCellReuseIdentifier:].
  • La vista de tabla le pide al controlador las celdas.
  • Probablemente llames -[UITableView dequeueReusableCellWithIdentifier:]
  • Cuando solicita una celda con un identificador de reutilización determinado, comprueba si tiene una punta registrada. Si lo hace, crea una instancia de esa célula. Esto se compone de los siguientes pasos:

    1. Mire la clase de la celda, como se define en la punta de la celda. Llamar [[CellClass alloc] initWithCoder:].
    2. El -initWithCoder:método pasa y agrega subvistas y establece propiedades que se definieron en la punta. ( IBOutlets probablemente también se enganche aquí, aunque no lo he probado; puede suceder en -awakeFromNib)
  • Configura su celda como quiera.

Lo importante a destacar aquí es que hay una distinción entre la clase de la célula y el aspecto visual de la celda. Podría crear dos celdas prototipo separadas de la misma clase, pero con sus subvistas presentadas de manera completamente diferente. De hecho, si usa los UITableViewCellestilos predeterminados , esto es exactamente lo que está sucediendo. El estilo "Predeterminado" y el estilo "Subtítulo", por ejemplo, están representados por la misma UITableViewCellclase.

Esto es importante : la clase de la celda no tiene una correlación uno a uno con una jerarquía de vista particular . La jerarquía de vistas está determinada completamente por lo que hay en la celda prototipo que se registró con este controlador en particular.

Tenga en cuenta, además, que el identificador de reutilización de la celda no estaba registrado en algún dispensario global de celdas. El identificador de reutilización solo se usa dentro del contexto de una sola UITableViewinstancia.


Dada esta información, veamos lo que sucedió en los intentos anteriores.

En el Controlador n. ° 1, agregué una celda prototipo, configuré la clase en mi subclase UITableViewCell, configuré el ID de reutilización, agregué las etiquetas y las conecté a las salidas de la clase. En el Controlador n. ° 2, agregó una celda prototipo vacía, configúrela en la misma clase y reutilice la identificación como antes. Cuando se ejecuta, las etiquetas nunca aparecen cuando las celdas se muestran en el Controlador # 2. Funciona bien en el controlador # 1.

Esto es de esperarse. Si bien ambas celdas tenían la misma clase, la jerarquía de vistas que se pasó a la celda en el Controlador # 2 estaba completamente desprovista de subvistas. Entonces tienes una celda vacía, que es exactamente lo que pones en el prototipo.

Diseñó cada tipo de celda en un NIB diferente y lo conectó a la clase de celda apropiada. En storyboard, agregué una celda prototipo vacía y configuré su clase y reutilicé la identificación para referirme a mi clase de celda. En los métodos viewDidLoad de los controladores, se registraron esos archivos NIB para la identificación de reutilización. Cuando se muestra, las células en ambos controladores estaban vacías como el prototipo.

De nuevo, esto se espera. El identificador de reutilización no se comparte entre las escenas o plumillas del guión gráfico, por lo que el hecho de que todas estas celdas distintas tuvieran el mismo identificador de reutilización no tenía sentido. La celda que recupere de la vista de tabla tendrá una apariencia que coincide con la celda prototipo en esa escena del guión gráfico.

Sin embargo, esta solución estaba cerca. Como notó, podría llamar programáticamente -[UITableView registerNib:forCellReuseIdentifier:], pasando el que UINibcontiene la celda, y volvería a esa misma celda. (Esto no se debe a que el prototipo estaba "anulando" la punta; simplemente no había registrado la punta con la vista de tabla, por lo que todavía estaba mirando la punta incrustada en el guión gráfico.) Desafortunadamente, hay un defecto con este enfoque: no hay forma de conectar los segmentos del guión gráfico a una celda en una punta independiente.

Los prototipos mantenidos en ambos controladores vacían y configuran la clase y reutilizan la identificación en mi clase de celda. Construyó la interfaz de usuario de las celdas completamente en código. Las células funcionan perfectamente en todos los controladores.

Naturalmente. Con suerte, esto no es sorprendente.


Entonces, por eso no funcionó. Puede diseñar sus celdas en puntas independientes y usarlas en múltiples escenas del guión gráfico; actualmente no puede conectar los segmentos del guión gráfico a esas celdas. Sin embargo, es de esperar que hayas aprendido algo en el proceso de leer esto.

BJ Homer
fuente
Ah, lo entiendo. Clavaste mi malentendido: la jerarquía de vistas es completamente independiente de mi clase. Obvio en retrospectiva! Gracias por la gran respuesta.
Cliff W
Ya no es imposible, parece: stackoverflow.com/questions/8574188/…
Rich Apodaca
77
@RichApodaca Mencioné esa solución en mi respuesta. Pero no está en el guión gráfico; Está en una punta separada. Por lo tanto, no podría conectar segues o hacer otras cosas storyboard-ish. Por lo tanto, no aborda totalmente la pregunta inicial.
BJ Homer
A partir de XCode8, la siguiente solución parece funcionar si desea una solución única de guión gráfico. Paso 1) Cree su celda prototipo en una vista de tabla en ViewController # 1 y asóciela con la clase UITableViewCell personalizada. Paso 2) Copie / Pegue esa celda en la vista de tabla de ViewController # 2. Con el tiempo, deberá recordar propagar manualmente las actualizaciones a las copias de la celda eliminando las copias que haya realizado en el guión gráfico y pegando en el prototipo actualizado.
jengelsma
Gran respuesta, tengo una pregunta de seguimiento:> "Un guión gráfico es, en esencia, no mucho más que una colección de archivos .xib". Si este es el caso, ¿por qué es tan difícil incrustar un xib en un guión gráfico?
willcwf
58

A pesar de la gran respuesta de BJ Homer, siento que tengo una solución. En lo que respecta a mis pruebas, funciona.

Concepto: cree una clase personalizada para la celda xib. Allí puede esperar un evento táctil y realizar la segue mediante programación. Ahora todo lo que necesitamos es una referencia al controlador que realiza la Segue. Mi solución es configurarlo tableView:cellForRowAtIndexPath:.

Ejemplo

Tengo una DetailedTaskCell.xibcelda de tabla que me gustaría usar en varias vistas de tabla:

DetailTaskCell.xib

Hay una clase personalizada TaskGuessTableCellpara esa celda:

ingrese la descripción de la imagen aquí

Aquí es donde sucede la magia.

// TaskGuessTableCell.h
#import <Foundation/Foundation.h>

@interface TaskGuessTableCell : UITableViewCell
@property (nonatomic, weak) UIViewController *controller;
@end

// TashGuessTableCell.m
#import "TaskGuessTableCell.h"

@implementation TaskGuessTableCell

@synthesize controller;

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    NSIndexPath *path = [controller.tableView indexPathForCell:self];
    [controller.tableView selectRowAtIndexPath:path animated:NO scrollPosition:UITableViewScrollPositionNone];
    [controller performSegueWithIdentifier:@"FinishedTask" sender:controller];
    [super touchesEnded:touches withEvent:event];
}

@end

Puedo tener varias Segues pero todos tienen el mismo nombre: "FinishedTask". Si necesita ser flexible aquí, le sugiero agregar otra propiedad.

El ViewController se ve así:

// LogbookViewController.m
#import "LogbookViewController.h"
#import "TaskGuessTableCell.h"

@implementation LogbookViewController

- (void)viewDidLoad
{
    [super viewDidLoad]

    // register custom nib
    [self.tableView registerNib:[UINib nibWithNibName:@"DetailedTaskCell" bundle:[NSBundle mainBundle]] forCellReuseIdentifier:@"DetailedTaskCell"];
}

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

    cell = [tableView dequeueReusableCellWithIdentifier:@"DetailedTaskCell"];
    cell.controller = self; // <-- the line that matters
    // if you added the seque property to the cell class, set that one here
    // cell.segue = @"TheSegueYouNeedToTrigger";
    cell.taskTitle.text  = [entry title];
    // set other outlet values etc. ...

    return cell;
}

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if([[segue identifier] isEqualToString:@"FinishedTask"])
    {
        // do what you have to do, as usual
    }

}

@end

Puede haber formas más elegantes de lograr lo mismo, ¡pero funciona! :)

ericteubert
fuente
1
Gracias, estoy implementando este enfoque en mi proyecto. En su lugar, puede anular este método para no tener que obtener el indexPath y seleccionar la fila usted mismo: - (nulo) setSelected: (BOOL) seleccionado animado: (BOOL) animado {[super setSelected: seleccionado animado: animado]; if (seleccionado) [self.controller performSegueWithIdentifier: self.segue sender: self]; } Pensé que super seleccionaría la celda al llamar a [super touchesEnded: touch withEvent: event] ;. ¿Sabes cuándo se selecciona si no está allí?
thejaz
99
Tenga en cuenta que con esta solución, está activando la segue cada vez que un toque termina dentro de una celda. Esto incluye si simplemente está desplazando la celda, en realidad no está intentando seleccionarla. Es posible que tenga mejor suerte anulando -setSelected:en la celda y desencadenando el segue solo cuando haga la transición de NOa YES.
BJ Homer
Tengo mejor suerte con setSelected:BJ. Gracias. De hecho, es una solución poco elegante (se siente mal), pero al mismo tiempo funciona, así que la estoy usando hasta que esto se arregle (o algo cambie en la corte de Apple).
Ben Kreeger
16

Estaba buscando esto y encontré esta respuesta de Richard Venable. Esto funciona para mi.

iOS 5 incluye un nuevo método en UITableView: registerNib: forCellReuseIdentifier:

Para usarlo, coloque un UITableViewCell en una punta. Tiene que ser el único objeto raíz en la punta.

Puede registrar la punta después de cargar su tableView, luego, cuando llame a dequeueReusableCellWithIdentifier: con el identificador de celda, lo extraerá de la punta, al igual que si hubiera usado una celda prototipo de Storyboard.

Odrakir
fuente
10

BJ Homer ha dado una excelente explicación de lo que está sucediendo.

Desde un punto de vista práctico, agregaría que, dado que no puede tener celdas como xib Y conectar segues, la mejor opción es tener la celda como xib: las transiciones son mucho más fáciles de mantener que los diseños y propiedades de la celda en múltiples lugares , y es probable que sus valores sean diferentes de sus diferentes controladores de todos modos. Puede definir el segue directamente desde su controlador de vista de tabla al siguiente controlador y realizarlo en código. .

Una nota adicional es que tener su celda como un archivo xib separado evita que pueda conectar cualquier acción, etc. directamente al controlador de vista de tabla (de todos modos, no he resuelto esto; no puede definir el propietario del archivo como algo significativo ) Estoy trabajando en esto definiendo un protocolo que se espera que el controlador de vista de tabla de la celda cumpla y agregando el controlador como una propiedad débil, similar a un delegado, en cellForRowAtIndexPath.

jrturton
fuente
10

Swift 3

BJ Homer dio una excelente explicación, me ayuda a entender el concepto. To make a custom cell reusable in storyboard, que puede usarse en cualquier TableViewController, tenemos que mix the Storyboard and xibacercarnos. Supongamos que tenemos una celda llamada como CustomCellque se utilizará en TableViewControllerOney TableViewControllerTwo. Lo estoy haciendo en pasos.
1. Archivo> Nuevo> Haga clic en Archivo> Seleccione Cocoa Touch Class> haga clic en Siguiente> Dar nombre de su clase (por ejemplo CustomCell)> seleccione Subclase como UITableVieCell> Marque la casilla también crear archivo XIB y presione Siguiente.
2. Personalice la celda como desee y configure el identificador en el inspector de atributos para la celda, aquí lo configuraremos como CellIdentifier. Este identificador se usará en su ViewController para identificar y reutilizar la celda.
3. Ahora solo tenemos queregister this cellen nuestro ViewController viewDidLoad. No es necesario ningún método de inicialización.
4. Ahora podemos usar esta celda personalizada en cualquier tableView.

En TableViewControllerOne

let reuseIdentifier = "CellIdentifier"

override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UINib(nibName: "CustomCell", bundle: nil), forCellReuseIdentifier: reuseIdentifier)
} 

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier:reuseIdentifier, for: indexPath) as! CustomCell
    return cell!
}
Kunal Kumar
fuente
5

Encontré una manera de cargar la celda para el mismo VC, no probé los segmentos. Esto podría ser una solución para crear la celda en una punta separada

Digamos que tiene una tabla de VC y 2 tablas y desea diseñar una celda en el guión gráfico y usarla en ambas tablas.

(por ejemplo, una tabla y un campo de búsqueda con un UISearchController con una tabla de resultados y desea utilizar la misma celda en ambos)

Cuando el controlador solicite la celda, haga esto:

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

    ContactsCell *cell = [self.YOURTABLEVIEW dequeueReusableCellWithIdentifier:identifier];
  // Ignore the "tableView" argument
}

Y aquí tienes tu celular del guión gráfico

João Nunes
fuente
Intenté esto y parece funcionar SIN EMBARGO, las células nunca se reutilizan. El sistema siempre crea y desasigna nuevas celdas cada vez.
GilroyKilroy
Esta es la misma recomendación que se puede encontrar en Agregar una barra de búsqueda a una vista de tabla con guiones gráficos . Si está interesado, hay una explicación más detallada de esta solución allí (buscar tableView:cellForRowAtIndexPath:).
Senseful
pero esto es menos texto y responde la pregunta
João Nunes