« Especial 11 años de Flash en Mosaic | Inicio | Arrastrar archivos sobre el icono de una aplicación »

Una de bindings

Utilizar bindings reduce significativamente el número de líneas de código necesarias para implementar el interfaz de una aplicación, sobre todo cuando se tiene un modelo que es una lista de entidades.

Sin embargo, como cualquier otro framework (aunque realmente los bindings no puedan denominarse como tal), requieren de cierta adaptación, de cierto conocimiento de sus detalles internos, para que su implementación sea exitosa.

Vamos a ver un caso concreto en el que no es suficiente, para tener una aplicación funcional, con crear el interfaz en Interface Builder, colocar un NSArrayController, y hacer los bindings entre el controlador del array y los elementos de interfaz.

La mayoría de ejemplos que pueden encontrarse, tanto en internet como en los libros de Cocoa sobre el uso de bindings se basan en crear una aplicación que pueda gestionar una colección de entidades, presentándolas por pantalla.

Normalmente, en estos casos, se colocan en el interfaz los botones necesarios para ilustrar cómo, gracias a los propios bindings, toda la gestión de la colección (altas, bajas, modificaciones) se puede realizar sin escribir una sola línea de código.

Sin embargo, ¿qué ocurre si lo que se quiere es presentar por pantalla una colección de entidades con unos valores iniciales, valores que no están disponibles en el momento en el que se inicializa el interfaz? ¿O si esos valores iniciales, se deben materializar de un grafo serializado a disco? Ciertamente, se podría migrar a Core Data, que porporciona, directamente, la serialización y materialización, pero no siempre se puede adoptar ese compromiso.

Por poner un ejemplo concreto voy a hacer una aplicación muy sencilla, que simplemente va a presentar una lista de textos en un NSTableView. El proyecto de XCode puede bajarse de aquí.

Bindings Interfaz Final

El primer paso es crear el proyecto en XCode. En este caso, voy a crear una aplicación Cocoa.

Bindings New Proyect


Una vez creado el proyecto, voy a modelar una entidad muy sencilla, que he llamado DNBindingEntity, que tiene una única variable de clase, llamada name, y muy importante, con sus dos accessors, muy necesarios para que los bindings funcionen. ¿Porqué son necesarios los accessors? Porque los bindings están basados en KVC (key value coding).

La cabecera será, por tanto:

#import <Cocoa/Cocoa.h>


@interface DNBindingEntity : NSObject
{
NSString *name;
}

-(NSString *) name;
-(
void) setName: (NSString *) aName;

@end

Y la implementación:

#import "DNBindingEntity.h"


@implementation DNBindingEntity

-(
id) init
{
self = [ super init ];

return self;
}

-(NSString *) name
{
return name;
}

-(
void) setName: (NSString *) aName
{
aName = [ aName copy ];
[ name release ];
name = aName;
}

-(
void) dealloc
{
[ name release ];
[
super dealloc ];
}
@end

Ahora, hay que crear un controlador de la aplicación. En Interface Builder, en la pestaña Classes, hay que crear una subclase de NSObject, instanciarla, y crear los archivos correspondientes.

Esa clase, va a tener solamente una variable, la lista de entidades (instancias de DNBindingsEntity), y los accessors correspondientes a esa lista.

La cabecera:

/* AppController */

#import <Cocoa/Cocoa.h>

@interface AppController : NSObject
{
NSMutableArray *list;
}

-(NSMutableArray *) list;
-(
void) setList: ( NSMutableArray *) aList;

@end

Antes de pasar a la implementación, hay que trabajar un poquito en el interfaz. Arrastra un NSTableView, y dimensiónalo. Arrastra también, a la ventana MainMenu.nib de Interface Builder, una instancia de NSArrayController. Haz doble clic sobre el nombre de la misma, y renómbrala como ListController.

Bindings Controller

Esa instancia de NSArrayController es la que se va a encargar de gestionar la colección de DNBindingsEntity, gracias a la "magia" de los bindings.

Pero para ello, lo primero que hay que hacer, es decirle a ese controlador dónde está la colección que tiene que gestionar. Así que selecciónalo, abre su panel de propiedades, y asgina el binding de contentArray, para que apunte a la propiedad list del controlador de la aplicación (AppController).

Bindings Controller Binding

Ahora hay que asignar el valor de las columnas del NSTableView. En mi caso, he dejado la primera columna en blanco (por ninguna razón en particular), y he asignado los bindings de la segunda columna de la siguiente manera.

Bindings Columna Binding

Lo que he hecho ha sido asignar a cada fila de esa columna el valor de la propiedad "name" de todos y cada uno (por eso arrangedObjects) de los elementos de la colección gestionada por ListController, el NSArrayController creado desde Interface Builder.

Al compilar la aplicación, en este punto, debería poderse ejecutar sin problemas, aunque que no presentará ningún dato.

¿Por qué no presenta datos? Es obvio: porque la colección de entidades (la propiedad list de AppController) está vacía.

Vamos a ver cómo dotar de contenido a esa colección, pero después de que se haya inicializado el interfaz de la aplicación. Es decir, vamos a inicializar el interfaz con una colección de valores vacía.

La implementación de la clase AppController, por tanto será muy sencilla.

En primer lugar, un método de inicialización:

-( id ) init
{
self = [ super init ];

return self;
}

Accesors para la propiedad list:

-(NSMutableArray *) list
{
return list;
}

-(
void) setList: ( NSMutableArray *) aList
{
if( aList != list )
{
[ list release ];
list = [ aList retain ];
}
}

Y un método dealloc:

-(void) dealloc
{
[ list release ];
[
super dealloc ];
}

Bien, ¿y si queremos, por ejemplo, cargar los datos de las entidades a presentar por pantalla de un servidor web, o materializarlos de disco... obtenerlos, a fin de cuentas, de cualquier proceso que no finalice hasta después de haber inicializado el interfaz?

Para simular el problema, vamos a crear el modelo de la aplicación en el método awakeFromNib de AppController, método que se ejecuta cuando ya se ha presentado el interfaz.

-(void) awakeFromNib
{

list = [ [ NSMutableArray alloc ] init ];

int i;


for( i =0; i< 4; i++ )
{
DNBindingEntity *ent = [ [ DNBindingEntity alloc ] init ];

[ ent setName: [ NSString stringWithFormat:
@"Entity number %i", i ] ];

[ list insertObject: ent atIndex: i ];

[ ent release ];
}

}

¿Qué ocurre si se compila la aplicación? Aparentemente nada, ya que la lista de entidades creada no aparece en el NSTableView.

Sin embargo la lista de entidades existe, lo que se puede comprobar si se crea un botón en el interfaz, y se hace Control+Drag desde el botón a ListController, y se selecciona como acción add:

Bindings Boton Add

Si se vuelve a compilar la aplicación, seguiá sin aparecer nada en el listado. No obstante, si se hace clic en el botón recién creado, se verá cómo se actualiza el interfaz, apareciendo la lista con las cuatro entidades previamente creadas.

¿Cuál es el problema entonces? Pues que los bindings funcionan gracias al key value coding. Cuando se inicializa el interfaz de la aplicación, la lista de entidades a la que está vinculado el listado del interfaz está vacía. Y aunque, acto seguido, se creen nuevos elementos de esa lista, esa creación de elementos no se hace a través de los accesors de la misma, y por tanto, el mecanismo de bindings no es notificado de los cambios en la propiedad list de AppController.

Si se modifica el método awakeFromNib, para que esas inserciones de nuevos elementos de la lista se hagan a través de los accesors:

-(void) awakeFromNib
{

list = [ [ NSMutableArray alloc ] init ];

int i;

NSMutableArray *actualData;

for( i =0; i< 4; i++ )
{
DNBindingEntity *ent = [ [ DNBindingEntity alloc ] init ];

[ ent setName: [ NSString stringWithFormat:
@"Entity number %i", i ] ];

actualData = [
self list ];

[ actualData insertObject: ent atIndex: i ];

[
self setList: actualData ];

[ ent release ];
}

}

Y se vuelve a compilar la aplicación... magia.

Resumiendo, y como decía al principio, no hay herramienta o framework, o solución de las que prometen mucha productividad a cambio de muy poco esfuerzo que no necesite de cierta adaptación, o de cierto conocimiento de su funcionamiento interno por el desarrollador.

Publicar un comentario

(Si no dejó aquí ningún comentario anteriormente, quizás necesite aprobación por parte del dueño del sitio, antes de que el comentario aparezca. Hasta entonces, no se mostrará en la entrada. Gracias por su paciencia).