Evito usar UITableViewController
, ya que pone muchas responsabilidades en un solo objeto. Por lo tanto, separo la UIViewController
subclase del origen de datos y delego. La responsabilidad del controlador de vista es preparar la vista de tabla, crear una fuente de datos con datos y unir esas cosas. Se puede cambiar la forma en que se representa la vista de tabla sin cambiar el controlador de vista y, de hecho, se puede usar el mismo controlador de vista para múltiples fuentes de datos que siguen este patrón. Del mismo modo, cambiar el flujo de trabajo de la aplicación significa cambios en el controlador de vista sin preocuparse por lo que sucede en la tabla.
Intenté separar los protocolos UITableViewDataSource
y UITableViewDelegate
en diferentes objetos, pero eso generalmente termina siendo una división falsa ya que casi todos los métodos en el delegado necesitan profundizar en la fuente de datos (por ejemplo, en la selección, el delegado necesita saber qué objeto está representado por el fila seleccionada). Así que termino con un solo objeto que es tanto el origen de datos como el delegado. Este objeto siempre proporciona un método en el -(id)tableView: (UITableView *)tableView representedObjectAtIndexPath: (NSIndexPath *)indexPath
que tanto la fuente de datos como los aspectos delegados necesitan saber en qué están trabajando.
Esa es mi separación de preocupaciones de "nivel 0". El nivel 1 se compromete si tengo que representar objetos de diferentes tipos en la misma vista de tabla. Como ejemplo, imagine que tenía que escribir la aplicación Contactos: para un solo contacto, podría tener filas que representan números de teléfono, otras filas que representan direcciones, otras que representan direcciones de correo electrónico, etc. Quiero evitar este enfoque:
- (UITableViewCell *)tableView: (UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath {
id object = [self tableView: tableView representedObjectAtIndexPath: indexPath];
if ([object isKindOfClass: [PhoneNumber class]]) {
//configure phone number cell
}
else if …
}
Dos soluciones se han presentado hasta ahora. Una es construir dinámicamente un selector:
- (UITableViewCell *)tableView: (UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath {
id object = [self tableView: tableView representedObjectAtIndexPath: indexPath];
NSString *cellSelectorName = [NSString stringWithFormat: @"tableView:cellFor%@AtIndexPath:", [object class]];
SEL cellSelector = NSSelectorFromString(cellSelectorName);
return [self performSelector: cellSelector withObject: tableView withObject: object];
}
- (UITableViewCell *)tableView: (UITableView *)tableView cellForPhoneNumberAtIndexPath: (NSIndexPath *)indexPath {
// configure phone number cell
}
En este enfoque, no necesita editar el if()
árbol épico para admitir un nuevo tipo, solo agregue el método que admite la nueva clase. Este es un gran enfoque si esta vista de tabla es la única que necesita representar estos objetos, o si necesita presentarlos de una manera especial. Si los mismos objetos se representarán en diferentes tablas con diferentes fuentes de datos, este enfoque se desglosa ya que los métodos de creación de celdas deben compartirse entre las fuentes de datos: puede definir una superclase común que proporcione estos métodos, o puede hacer esto:
@interface PhoneNumber (TableViewRepresentation)
- (UITableViewCell *)tableView: (UITableView *)tableView representationAsCellForRowAtIndexPath: (NSIndexPath *)indexPath;
@end
@interface Address (TableViewRepresentation)
//more of the same…
@end
Luego, en su clase de fuente de datos:
- (UITableViewCell *)tableView: (UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath {
id object = [self tableView: tableView representedObjectAtIndexPath: indexPath];
return [object tableView: tableView representationAsCellForRowAtIndexPath: indexPath];
}
Esto significa que cualquier fuente de datos que necesite mostrar números de teléfono, direcciones, etc. simplemente puede preguntar cualquier objeto representado para una celda de vista de tabla. La fuente de datos en sí ya no necesita saber nada sobre el objeto que se muestra.
"Pero espera", escucho una interposición hipotética del interlocutor, "¿eso no rompe MVC? ¿No estás poniendo los detalles de la vista en una clase de modelo?"
No, no rompe MVC. Puede pensar en las categorías en este caso como una implementación de Decorator ; así que PhoneNumber
es una clase de modelo pero PhoneNumber(TableViewRepresentation)
es una categoría de vista. La fuente de datos (un objeto controlador) media entre el modelo y la vista, por lo que la arquitectura MVC aún se mantiene.
También puede ver este uso de categorías como decoración en los marcos de Apple. NSAttributedString
es una clase modelo, que contiene texto y atributos. AppKit proporciona NSAttributedString(AppKitAdditions)
y UIKit proporciona NSAttributedString(NSStringDrawing)
categorías de decorador que agregan comportamiento de dibujo a estas clases de modelos.