« Septiembre 2007 | Inicio | Noviembre 2007 »

Octubre 28, 2007

Un clon de Photo Booth en 32 minutos (con un café a la mitad)

Una de las novedades de Leopard es la inclusión de un framework, ImageKit, dedicado a proveer al desarrollador de varias vistas dedicadas a la presentación de imágenes.

Como parte el framework, se incluyen dos vistas, IKImageBrowserView e IKImageView, dedicadas a la presentación, respectivamente, de colecciones de imágenes, y a una imagen de forma individual. Y por imagen, se entiende cualquier tipo soportado por QuickTime, lo que incluye vídeos y pdfs.

Para intentar ilustrar lo potente de estas nuevas APIs, vamos a hacer un ligero ejercicio de abstración.

El Reto

Hong Kong, 2007. Bruce Lee reta a El Gran Dragón, su discípulo más aventajado, a desarrollar un clon, con ciertas capacidades mermadas, de Photo Booth, la aplicación de Apple incluída con el sistema operativo. La merma de capacidades está, sobre todo, en la serialización del modelo a disco, o mejor dicho, en la falta del mismo. Vamos, que los datos no se guardan entre sesiones.

Clon 14 Funcionando

Clon 15 Funcionando

Así pues, El Gran Dragón va a necesitar de todos sus DeveloperSkillz y de la ayuda de algunas de las nuevas clases de Leopard, como IKPictureTaker, que expone y encapsula el proceso de captura de imágenes y vídeos utilizando la iSight incorporada en muchos Mac.

Continuemos el ejercicio de abstracción. El Gran Dragón y Bruce están en un ring, en un pabellón lleno hasta la bandera de público dispuesto a presenciar un espectáculo inolvidable...

17:55 El Gran Dragón se sienta ante la máquina. Va a ser una tarde muy, muy intensa.

17:56. El Gran Dragón crea un nuevo proyecto en XCode. En un alarde de sangre fría, elige como tipo de proyecto, una aplicación Cocoa (en Objective-C, por supuesto). Entre el público se escapan varios murmullos de sorpresa...

Clon 1

17.57. XCode crea la estructura del proyecto. El Gran Dragón, presa de la impaciencia, hace dobe click sobre MainMenu.nib para editarlo en el nuevo, brillante, y deliciosamente bonito Interface Builder.

17.58 El brillo de la nueva interfaz de Interface Builder no distrae a El Gran Dragón de su misión. Y precisamente, porque no se distrae, El Gran Dragón recuerda que debe linkar el framework Quartz a su proyecto, para que éste pueda compilar sin errores.

Clon 2 Add Framework

Por tanto, vuelve a XCode, presiona opción + comando + A (Project-> Add to Project) y navega hasta la ubicación de Quartz (/Sistema/Libreria/Frameworks/Quartz.framework), y da la orden, a la velocidad del rayo, de añadirlo al proyecto (sin copiarlo al mismo). Entre el público hay un par de amagos de desmayo.

18:00 Es el momento de comenzar a escribir código. Como El Gran Dragón hacer gala de su productividad y su rapidez con el teclado, crea directamente la clase del controlador de la aplicación en XCode (File_> New File -> Objective-C class) y la asigna el original nombre de PBController (Photo Bluff Controller). A la asingación del nombre le siguen ciertos murmullos de desaprobación entre el público. ¡Qué sabran esos!

18:01 El Gran Dragón añade dos outlets (uno para el listado de imágenes, llamado browser, y otro para la vista en detalle de cada una de las imágenes llamado view) a la cabecera de la clase. También añade un action. También importa Quartz De este modo, la cabecera (el fichero PBController.h) queda de la siguiente forma:

#import <Cocoa/Cocoa.h>
#import
<Quartz/Quartz.h>


@interface PBController : NSObject
{
IBOutlet IKImageBrowserView *browser;
IBOutlet IKImageView *view;
}

- (
IBAction)captureImage:(id)sender;

@end

18:02 EL Gran Dragón, con un movimiento felino, se pasa a Interface Builder. Añade un objeto al nib, y le asigna como nombre de clase, en el panel de Identity, PCController. Inmediatamente, Interface Builder parsea la cabecera y reconoce la existencia de los outlets y del action.

Clon 3 Object

18:03 Es el momento de trabajar en el interfaz. El Gran Dragón hace un clic en la instancia de NSWindow que ya está abierta en Interface Builder, y modifica uno de sus atributos en el panel de atributos, para hacerla Textured. ¿Por qué? Por que sí, El Gran Dragón no da explicaciones a nadie...

Clon 5 Textured

18:03 y un poquito más. ¡La hora Toolbar! Porque ahora se pueden diseñar los Toolbars directamente en Interface Builder. El Gran Dragón arrastra una instancia de NSToolbar sobre la ventana de su aplicación, y en la misma ventana presiona el botón de "Personalizar". Sobre la ventana de edición del Toolbar, arrastra una instancia de NSToolbarItem, y lo coloca en la posición más a la izquierda de todos los elementos del toolbar.. La sala estalla en aplausos. Pero aún o ha llegado lo mejor. El Gran Dragón hace control+drag desde el elemento que acaba de crear en el Toolbar hasta el controlador, y conecta el mismo con el action que creó al principio de su desafío: captureImage: A continuación, elimina los elementos sobrantes, hasta que su barra de herramientas queda como la de la imagen

Clon 7 Toolbarfinal

18:05. El Gran Dragón sabe que es el momento de arrastrar una instancia de IKImageBrowserView y colocarla en la ventana de su aplicación. Así lo hace, dejando espacio por la parte inferior para la instancia de IKIMageView. Tras arrastrar ambas vistas, su interfaz tiene este aspecto:

18:06 Hay que empezar a aproximar el interfaz a su aspecto final. Lo primero es añadir una barra de scroll para el browser. Así pues, entre suspiros de las damas, El Gran Dragón selecciona el browser y hace clic en Layout->Embed Objects In -> Scroll View, y casi en el mismo paso, edita las propiedades de la nueva ScrollView de scroll para eliminar la barra de scroll horizontal. Y, más aún, entre gritos de emoción de los caballeros, ajusta el tamaño del conjunto browser y barra de scroll para que ocupe todo el ancho de la ventana.

18:07 El Gran Dragón hace también la instancia de IKImageView del ancho de la ventana. Posteriormente selecciona las dos vistas, y las envuelve en un SplitView (Layout ->Embed Objects In -> Split View) También, en el panel de propiedades, ajusta el autosizing del Split View para vincularlo al tamaño de la ventana. El aspecto final de su interfaz es...

Clon 9 Interfaz Final 3

18:08 Aún falta asignar los outlets del controlador. Por eso El Gran Dragón lanza un par de latigazos de control+drag desde el controlador hasta, respectivamente, el browser y la vista de la imagen, finalizando la asignación. También, hace que el controlador de la aplicación sea el dataSource del browser.

18:09 El Gran Dragón se hace pis, así que hace una parada que aprovecha el público para ir al bar.

18:12 No ha estado mal. Es el momento de volver a XCode. Es el momento de empezar a implementar el controlador de la aplicación. Lo primero es añadir una property para guardar la colección de datos de la aplicación. También declara el getter y el setter de esa propiedad, así como un método llamadao addImageWithPath y dos de los métodos del protocolo que implementa el dataSource del browser. También, porque le gusta hacerlo así, modifica la declaración de los outlets, para asignarles el tipo adecuado. La cabecera, por tanto, quedará así:

#import <Cocoa/Cocoa.h>
#import
<Quartz/Quartz.h>


@interface PBController : NSObject
{
IBOutlet IKImageBrowserView *browser;
IBOutlet IKImageView *view;
NSMutableArray *list;
}

- (
IBAction)captureImage:(id)sender;


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

-(
void) addImageWithPath: (NSString *) aPath;

-(
int) numberOfItemsInImageBrowser: (IKImageBrowserView *) aBrowser;
-(
id) imageBrowser: (IKImageBrowserView *) aBrowser
itemAtIndex: (
NSUInteger) index;
@end

18:14 Llega el momento para El Gran Dragón de empezar a implementar el controlador. Como le gusta empezar por el principio, primero añade el método de inicialización, donde inicializa (que por algo el método se llama como se llama) el array de datos:

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

return self;
}

A continuación, pasa al método awakeFromNib, el que se ejecuta cuando se ha terminado de desplegar e inicializar todos los objetos incluídos en el nib. En ese método, El Gran Dragón invita amablemente al browser, ante el asombro del público, a que permita reordenar sus elementos, a que presente esas reordenaciones de forma animada, y a que su delegate sea el propio controlador:

-( void ) awakeFromNib
{
[
browser setAllowsReordering: YES ];
[
browser setAnimates: YES ];
[
browser setDelegate: self ];
}

18:16 Como va sobrado de tiempo, El Gran Dragón se pone a nevegar un rato por internet.

18:17 El Gran Dragón añade el getter y el setter de list:

-(
NSMutableArray *) list
{
return list;
}

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

}

Y los dos métodos del dataSource:


-(
int)numberOfItemsInImageBrowser:(IKImageBrowserView *)aBrowser
{
return [ list count ];
}

-(
id) imageBrowser: (IKImageBrowserView *) aBrowser
itemAtIndex: (
NSUInteger) index
{
return [ list objectAtIndex: index ];
}

18:20 Hay que hacer limpieza antes de terminar...

-(
void) dealloc
{
[
list release ];

[
super dealloc ];
}

18:21 Es la hora de comenzar a programar en serio. Para que el IKImageBrowserView presente correctamente elementos en su interior, se le debe pasar, como valor de retorno de

-(
id) imageBrowser: (IKImageBrowserView *) aBrowser
itemAtIndex: (
NSUInteger) index

un objeto que implemente el protocolo IKImageBrowserItem. Eso implica que hay que crear una entidad que modele a cada una de las imágenes a presentar en el browser. En realidad no a la imagen en sí, sino que simplemente sería necesario encapsular el path de la imagen. El Gran Dragón, por tanto, pasa a modo "modelo".

18:21 y un poco más. El público está en completo silencio. El Gran Dragón escribe la cabecera de su entidad:

#import <Cocoa/Cocoa.h>
#import
<Quartz/Quartz.h>

@interface DataSourceItem : NSObject
{
NSString* path;
}

-(
void)setPath:(NSString*)inPath;
-(
NSString * ) path;
@end

En la implementación de la clase, escribe el getter y el setter del path:

- (
void)setPath:(NSString*)inPath
{
if (path != inPath)
{
[
path release];
path = [inPath retain];
}
}
-(
NSString *) path
{
return path;
}

Y los tres métodos a los que le obliga el protocolo IKImageBrowserItem

- (
NSString*)imageRepresentationType
{
return IKImageBrowserPathRepresentationType;
}

- (
id)imageRepresentation
{
return path;
}

- (
NSString*)imageUID
{
return path;
}

18:22 El Gran Dragón hace un alto para reflexionar. Al devolver como imageRepresentatonType de esta entidad la constante IKImageBrowserPathRepresentationType le está diciendo a quien quiera consumir su interfaz que lo que le van a devolver como imageRepresentation va a ser el path del fichero correspondiente. COmo así ocurre, por cierto. ¿Qué otras cosas se pueden devolver? Pues una representación tiff, un quicktime, un pdf...

18:23 Se acabó la reflexión. Es el momento de implementar le método que se va a ejecutar cuando se haga clic en el item del toolbar: captureImage. Ese método obtendrá una referencia a IKPictureTaker (que, como cabe esperar es un singleton), y setea una serie de parámetros iniciales del mismo (el área de cropping, la imagen inicial, y si se debe o no mostrar el botón de "elegir archivos" y si se deja o no aplicar efectos a la imagen capturada).

También, se da la orden de lanzar el PictureTaker, pasando como parámetro el selector que se quiere ejecutar al finaizar la captura.

- (
IBAction)captureImage:(id)sender
{
IKPictureTaker *pictureTaker = [IKPictureTaker pictureTaker];

[pictureTaker
setInputImage: [ [ [ NSImage alloc] initByReferencingFile:@"/Library/Desktop Pictures/Black & White/Sea Mist.jpg" ] autorelease]];
[pictureTaker
setValue: [ NSValue valueWithSize:NSMakeSize(300, 200)] forKey:IKPictureTakerCropAreaSizeKey ];
[pictureTaker
setValue: [ NSNumber numberWithBool: NO ] forKey:IKPictureTakerAllowsFileChoosingKey ];
[pictureTaker
setValue: [ NSNumber numberWithBool:YES ] forKey:IKPictureTakerShowEffectsKey ];

[pictureTaker
beginPictureTakerWithDelegate:self didEndSelector:@selector(pictureTakerValidated:code:contextInfo:) contextInfo:nil];
}

18:25 Como El Gran Dragón se ha gustado con lo del singleton, y sigue estando sobrado, se va a tomar un café. Con leche. Sin azúcar.

18:26 El Gran Dragón comienza con la implementación del método que se ejecutará cuando se cierre el panel. Ese método comprobará si el botón que se ha clickado en el panel para cerrarlo ha sido el de OK, y en ese caso, obtendrá una referencia a la imagen capturada, creará una cadena de texto con el path de la imagen a escribir (con un autocontador embebido en el mismo), y llamará a otro método del controlador, encargado de crear la instancia de DataSourceItem y añadirla al modelo de la aplicación. Así pues:

- (
void) pictureTakerValidated:(IKPictureTaker*) pictureTaker code:(int) returnCode contextInfo:(void*) ctxInf
{
if(returnCode == NSOKButton){
NSImage *outputImage = [ pictureTaker outputImage ];

NSString *outputPath = [ [ NSString stringWithFormat: @"~/Pictures/cameracapturer-snap-%i.tiff", [ list count ] ] stringByExpandingTildeInPath ];
[ [ outputImage TIFFRepresentation ]
writeToFile: outputPath atomically:YES ];

[
self addImageWithPath: outputPath ];

}
else{
//cancelado por el usuario, no hacer nada
}
}

18:27 El paso siguiente es obvio: debe crearse el método que añade la imagen al modelo, y mandar recargar el browser:
-(
void) addImageWithPath: (NSString *) aPath
{
DataSourceItem *item = [ [ DataSourceItem alloc ] init ];
[ item
setPath: aPath ];

[
list addObject: item ];

[ item release ];

[
browser reloadData ];
}

En este momento, El Gran Dragón compila la aplicación. Ante el asombro general, hace clic en el botón del toolbar y se abre el panel de captura. Oooooooooooooooooohs y aaaaaaaaaaaaaaaaaaahhhhhs acompañan a la primera imagen que El Gran Dragón captura de sí mismo:

Clon 15 Funcionando-1


imagen que aparece en el browser como por arte de magia.

18:29 El Gran Dragón abandona su puesto para ayudar a los sanitarios que han entrado en la sala a ayudar a una señora que se ha desmayado ante tanta tensión. A continuación, implementa otro método del delegate del browser, el que se ejecuta cuando se ha seleccionado alguno de los componentes del mismo, apra ofrecer una vista ampliada de la imagen en el IKImageVIew. El método es:

-(
void) imageBrowserSelectionDidChange:(IKImageBrowserView *) aBrowser
{
NSIndexSet *actualSelection = [ browser selectionIndexes ];
if( [ actualSelection count ] == 1 )
{
int index = [ actualSelection firstIndex ];
NSString *path = [ [ list objectAtIndex: index ] path ];
NSURL *imageURL = [ NSURL fileURLWithPath: path ];
//NSLog( @"setting la vista grande %@", imageURL );
[
view setImageWithURL: imageURL ];
}
}

Como puede verse, la cosa es sencilla. Si sólo se ha seleccionado una imagen, se obtiene el valor del path de la misma, que se pasa a la vista de la imagen. El resultado...

Clon 12 Funcionando

IKImageView implementa un HUD en el que se pueden modificar bastantes parámetros de la imagen (exposición, contraste) e incluso aplicarla efectos. Todo, directamente para su disfrute nada más sacarla de la caja.

18:31 Ya sólo falta el último paso: implementar la reordenación de las imágenes del browser. Tampoco tiene tanto misterio, se trata de implementar el método

- (
BOOL) imageBrowser:(IKImageBrowserView *) aBrowser
moveItemsAtIndexes: (
NSIndexSet *)indexes
toIndex:(
NSUInteger)destinationIndex

del protocolo del dataSource. Lo que hay que hacer es obtener el set con los índices de los elementos seleccionados, así como el índice donde se van a soltar. Luego, hay que crear un array temporal en el que colocarán los elementos arrastrados, mientras se eliminan del original. Posteriormente, ese array se inserta en la posición en la que se soltaron las imágenes, y se recarga el browser:

- (
BOOL) imageBrowser:(IKImageBrowserView *) aBrowser
moveItemsAtIndexes: (
NSIndexSet *)indexes
toIndex:(
NSUInteger)destinationIndex
{
NSInteger index;
NSMutableArray* tempArray;

tempArray = [ [ [
NSMutableArray alloc ] init ] autorelease ];

for(index = [ indexes lastIndex ]; index != NSNotFound; index = [ indexes indexLessThanIndex:index ] )
{
if (index < destinationIndex)
destinationIndex --;

id obj = [ list objectAtIndex:index ];
[ tempArray
addObject:obj ];
[
list removeObjectAtIndex:index ];
}

// Then insert the removed items at the appropriate location.
NSInteger n = [ tempArray count ];
for( index = 0; index < n; index++ )
{
[
list insertObject: [ tempArray objectAtIndex: index ] atIndex: destinationIndex ];
}

return YES;
}


18:32 El ruido en la sala es ensordecedor. La multidud, enardecida, asalta el ring y se lleva a El Gran Dragón a hombros, como si fuera un torero. Acaba de nacer una leyenda...

El proyecto completo se puede descargar de su repositorio:

svn co http://svn.liadorasoft.com/photobluff Photo_Bluff

Octubre 08, 2007

Arrastrar archivos sobre el icono de una aplicación

Una de las cosas que caracterizan a Mac OS X es la cantidad de tareas que se pueden realizar arrastrando y soltando. Por eso, es importante que cualquier aplicación ofrezca el soporte para drag and drop que todo usuario espera.

Una de las formas de dar ese soporte es permitir que se puedan arrastrar archivos sobre el icono de una aplicación en el Dock. De esa forma, los archivos arrastrados pasarán al flujo de trabajo de la aplicación en cuestión.

Dar ese soporte es bastante sencillo. Vamos a verlo con un ejemplo.

Crea una nueva aplicación Cocoa. En mi caso, la aplicación se llama FilesOverIcon, nombre original hasta decir basta.

Una vez creado el proyecto en XCode, hay que editar las propiedades del Target de la aplicación, para incluir en ellas los tipos y extensiones de archivos soportados. ¿Soportados por quién? Soportados por la instancia de NSApplication que será mi aplicación. En mi caso, he declarado como extensiones válidas las *.jpg, *.png y *.nef:

Post Icon Tipes

Ahora, voy a crear el controlador de la aplicación. Por tanto, hay que abrir Interface Builder y crear una subclase de NSObject (que he llamado FilesOverIconController)

Post Icon Controller

A continuación hay que crear una instancia de esa clase y los archivos correspondientes a la misma. Una vez creada la instancia, hay que declarar esa clase como delegate de NSApplication, lo que se consigue haciendo Control+Drag desde File's Owner hasta la instancia del controlador:

Post Icon Delegate

Y asignando posteriormente el outlet correspondiente:

Post Icon Delegate 2

Ya casi está. Ahora sólo falta declarar en el controlador el método correspondiente del delegate:

#import "FilesOverIconController.h"

@implementation FilesOverIconController

- (
BOOL)application:(NSApplication *)sender openFile:(NSString *)path
{
NSLog(
@"files dropped over icon %@", path );

return YES;
}
@end

Ese método será ejecutado cada vez que se suelte sobre el icono de la aplicación un fichero de alguno de los tipos que previamente se declararon como válidos. No todos los ficheros que se suelten han de ser válidos, aquellos que no lo sean, simplemente serán ignorados por NSApplication.

Por si alguien está interesado en el código fuente, lo puede descargar del repositorio de Subversion:

svn co http://svn.liadorasoft.com/filesovericon

Octubre 04, 2007

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.

Octubre 02, 2007

Especial 11 años de Flash en Mosaic

Mosaic, la revista del Graduado Multimedia de la UOC celebra el undécimo aniversario del nacimiento de Flash con un número especial con bastante chicha.

Abre fuego una entrevista con Carlos Ulloa, el timonel de Papervision 3D, seguido por un artículo de Jaume Gil sobre los problemas que se puede encontrar cualquier desarrollador al comenzar a trabajar con Flash, y termina con un repaso, a cargo de un servidor, de la historia de la herramienta, no exenta de paralelismos con otros lenguajes o plataformas de desarrollo.

Todas ellas lecturas interesantes, creo, tanto para los que están en este lado de la barrera, como para los que miran hacia aquí, sea con curiosidad, o sea con desprecio.