Creador y propietario de AdictosAlTrabajo.com, Director General de Autentia S.L., Ingeniero Técnico de Telecomunicaciones y Executive MBA por el Instituto de Empresa 2007.
Twitter: Follow @rcanalesmora
Autor de los Libros: Planifica tu éxito: de aprendiz a empresario y Informática profesional, las reglas no escritas para triunfar en la empresa
Puedes consultar mi CV y alguna de mis primeras aplicaciones (de los 90) aquí
Fecha de publicación del tutorial: 2010-02-16
Puedes descargar el código fuente aquí
Aprendiendo Objetive-C desarrollando para nuestro Iphone 3Gs
Antecedentes
Julián, mi proveedor de mamparas y muebles de oficina ( http://www.provimobelmamparas.com ) me dijo hace meses que el iphone era el
mejor teléfono que había tenido. Al principio no me parecía la mejor opción por el tamaño y fundamentalmente por no tener GPS.
Yo era más bien de htc, y he tenido casi toda la gama. Al surgir el modelo 3Gs me he animado a coger uno y … es el mejor móvil
que he tenido nunca. De hecho, otra vez me doy cuenta de que no tenía razón y de haberlo adquirido antes (incluso el modelo
anterior) seguro que me hubiera interesado en su programación hace tiempo.
Lo bueno de no ser el primero es que ya hay multitud de ejemplos y libros sobre el tema por lo que "no pagas tanto las
novatadas". Además, desde que algo surge hasta que las empresas en España lo demanden creedme que pasan muchos meses.
He visto que algunos miembros de Autentia tienen también el htc con Android con unas sensaciones igual de buenas y …
parece que hay una feroz competencia que dará mucho juego. Android tiene como ventaja de partida para nosotros los Javeros, que
se programa en Java pero corremos también el peligro de entrar en el anti-patrón golden hammer (para un martillo de oro todos son
clavos): No estaría bien elegir un dispositivo solamente porque usa el lenguaje que conocemos…
De momento me ha gustado la vistosidad de Iphone y como el lenguaje y entorno me es completamente nuevo estoy bastante
picadillo porque veo decenas de posibles combinaciones con las aplicaciones empresariales que habitualmente nos piden. Aunque
trabajemos habitualmente con JSF, Spring y cosas similares: interfaces ricos basados en Iphone o android y tirando de servicios
Web y de nuestros componentes SOA, no parece descabellado. Adicionalmente para fortalecer el interés en aprender estas cosas, creo
que el IPad (sobre todo una segunda generación futura que me arriesgaría a apostar sale a los pocos meses del primero) nos
abrirán un mundo de posibilidades cambiando la percepción del interfaz de usuario… el tiempo lo dirá.
Aunque hay centenares de documentos cuanto te registras en la Web de Mac, empezar me ha requerido leerme un montón de cosas e
incluso buscar por internet hasta cuadrar un poco los conceptos. En este tutorial pretendo presentar un poquito el lenguaje,
un planteamiento básico de aplicación y las herramientas más comunes a la hora de desarrollar. Siendo egoísta, este tutorial
me valdrá a mí para copiar y pegar en otros ejemplos porque al principio la notación se hace un poco extraña por el vicio de
Java. Esto es como sacarte el carnet de conducir cuando has llevado desde los 15 años coches por el campo … tienes muchos
vicios.
Por cierto, que no se os olvide visitar el tutorial anterior que tengo sobre esto. Seguro que os quedarán las cosas más claras.
http://www.adictosaltrabajo.com/tutoriales/tutoriales.php?pagina=scrumiphone
Ejemplo a realizar
En mis cursos de UML y orientación a objetos, pongo siempre un ejemplo de lo simple que puede plantearse un sistema para pintar si tenemos claros los conceptos de polimorfismo.El sistema consistirá en:
- Una clase base que represente un objeto gráfico y defina los atributos comunes y los métodos a redefinir. Será CObjetoGrafic
- Una clase derivada por cada objeto gráfico a pintar: Básicamente como un patrón estrategia. Serán CRectangulo y CElipse.
- Un vista que controle los eventos del dedo (o ratón en el simulador) que diga donde pintar.
- Unos botones que nos ayuden a definir si pintamos una elipse o un rectángulo.
- Un controlador que reciba los eventos de los botones y establezca el tipo de elemento a pintar.
Entorno
MacBook pro 13 pulgadas con pantalla auxiliar (es fundamental para trabajar cómodo).

Developer Information:
Version: 3.2 (10M2003)
Location: /Developer
Applications:
Xcode: 3.2.1 (1613)SDKs:
Interface Builder: 3.2.1 (740)
Instruments: 2.0.1 (1096)
Dashcode: 3.0 (328)
Mac OS X:
10.5:(9J61)
10.6:(10M2003)
iphone OS:
2.2.1: (5H11)
3.0:(7A341)
3.1:(7C144)
3.1.2:(7D11)
iphone Simulator:
3.0:(7A341)
3.1:(7C144)
3.1.2:(7D11)
Creación del esqueleto del proyecto con el wizard de xcode
Creamos el proyecto basado en una vista y lo llamamos PintadorIphone


Pintando el icono con Icon Composer.
Vamos a revisar las herramientas que vienen con el Kit de desarrollo para el Iphone y encontramos ésta: Icon Composer.




También lo guardamos como Default.png para que nos valga de pantalla flash (aunque podría ir aquí publicidad o algo más vistoso)



Añadiendo clase de vista
Nosotros vamos a hacer un programa que pinte atendiendo a los toques de la pantalla táctil de nuestro Iphone o simulando con el ratón en el emulador.Al crear el proyecto desde el asistente, se crea un fichero que contiene nuestra vista. Ahora bien, como queremos redefinirla, necesitamos el código fuente de la clase derivada de UIView para, a través de polimorfismo de nuevo, redefinir los métodos deseados.
Nos vamos a xcode y en el menú file creamos un nuevo fichero (New Empty File)







Establecer la clase manejadora de la vista
Ahora nos vamos al interfaz gráfico pinchando en cualquier fichero xib (mejor el controlador). Veremos que la vista (View a la izquierda) es de tipo UIView. Lo cambiamos a CAreaPintadoView.

Redefinimos el método initWithCoder que es invocado al crear las vista. Es un buen punto para coger otros recursos que podamos tener almacenados dentro del fichero nib.
//
// CAreaPintadoView.m
// Pintadoriphone
//
// Created by rcanales on 13/02/10.
// Copyright 2010 Autentia. All rights reserved.
//
#import "CAreaPintadoView.h"
@implementation CAreaPintadoView
// Metodo invocado cuando se carga desde un nib -initWithCoder:
- (id)initWithCoder:(NSCoder*)coder {
[super initWithCoder:coder]; // no olvidar invocar al padre para que se pinte el nib
NSLog(@"Cargamos desde nib, buen momento para inicializar cosas");
return self;
}
Manejarnos por la documentación y pdfs recomendados
Es vital saber saltar rápidamente a la documentación. Elegimos una palabra y pulsamos el botón derecho

Ésta es una colección de los pdfs que os recomiendo que encontréis.

Como se suele decir, para estar bella hay que sufrir… pues para saber, hay que estudiar.
Construir y ver lo que pasa en la consola.
El método NSLog vuelca en la consola mensajes que nos puede permitir ver de un modo cómodo qué pasa.


Si estáis acostumbrados a C/C++ os vendrá a la idea de un modo inmediato el problema… problemas de linkado (perdonad el palabro).
En Bulid pulsamos Clean All Targets





Creando el comportamiento polimórfico
Queremos que todas las clases que pinten tengan la función pintate y que reciban el contexto de dibujo. Creamos un protocolo a tal fin llamado IPintable
// IPintable.h // Pintadoriphone // // Created by rcanales on 13/02/10. // Copyright 2010 Autentia. All rights reserved. // #import < Foundation/Foundation.h> @protocol IPintable // es el equivalente a un interfaz en java @required // declaramos el método como requerido -(void) pintate:(CGContextRef *) contexto; @endAhora vamos a crear la clase CObjetoGrafico que implementa pintable y que tiene las variables x,y,ancho y alto. También construimos un inicializador que por defecto empieza por init y que retorna self.
También proporcionamos un método auxiliar de traza para ver el estado de las variables sin recurrir al depurador.

#import < Foundation/Foundation.h>
#import "IPintable.h"
@interface CObjetoGrafico : NSObject < IPintable> { // No se puede declarar una clase abstracta en Objetive-C
@protected // atributos protegidos porque queremos usarlos en clases derivadas polimorficas
NSInteger x; // punto x inicial donde pintar la figura
NSInteger y; // punto y inicial donde pintar la figura
NSInteger ancho; // ancho de la figura
NSInteger alto; // alto de la figura
}
// ojo que las propiedades no assign hay que liberarlas en dealloc
@property NSInteger x;
@property NSInteger y;
@property NSInteger ancho;
@property NSInteger alto;
// creamos el inicializador con los nombres de los atributos
-(id) initConValores:(NSInteger) px y:(NSInteger)py ancho:(NSInteger)pancho alto:(NSInteger)palto;
-(void) traza; // metodo para volcar en la consola el contenido de las variables
@end
// Como repaso, si queremos utilizar métodos particulares en clases hijas, recondar:
// isMemberOfClass verifica si es de una clase concreta
// isKindOfClass verifica si es de una derivada
Vemos ahora la implementación en el fichero .m

//
// CObjetoGrafico.m
// Pintadoriphone
//
// Created by rcanales on 13/02/10.
// Copyright 2010 Autentia. All rights reserved.
//
#import "CObjetoGrafico.h"
@implementation CObjetoGrafico
@synthesize x; // getters y setters automaticos
@synthesize y; // getters y setters automaticos
@synthesize ancho; // getters y setters automaticos
@synthesize alto; // getters y setters automaticos
// inicializamos la clase base
-(id) initConValores:(NSInteger) px y:(NSInteger)py ancho:(NSInteger)pancho alto:(NSInteger)palto
{
self = [super init]; // leer convenciones
self.x = px;
self.y = py;
self.ancho = pancho;
self.alto = palto;
return self; // simpre retornar self
}
-(void) traza // mostramos el contenido
{
NSLog(@"valor X: %d",x);
NSLog(@"valor Y: %d",y);
NSLog(@"valor Ancho: %d",ancho);
NSLog(@"valor Alto: %d",alto);
NSLog(@"--------------");
}
// método base que pinta un rectangulo por defecto
-(void) pintate:(CGContextRef *) contexto
{
}
@end
La clase CRectangulo hacemos que herede de CObjetoGrafico y hacemos lo mismo con CElipse.

Ver la relación de clases como modelo UML
Si pinchamos en la raíz del proyecto y damos a Design > Class Model > Quick Model


Creando botones en la vista y la acción ejecutora para cambiar el elemento a pintar.
Si volvemos al builder, arrastamos una barra de navegación y dos botones. No es que sea tal vez el control más adecuado pero queremos hacerlo muy simple.Ponemos en los botones los títulos Elipse y Rect (ignorar que pone rectángulo o si cambiáis el título que sepáis que tiene impacto el el código posterior).
Si os fijáis a la derecha en Library, hemos elegido la clase PintadoriphoneViewController y hemos creado la acción botonPulsado que retorna un UIBarButtonItem (por defecto es un id).


//
// PintadoriphoneViewController.h
// Pintadoriphone
//
// Created by rcanales on 13/02/10.
// Copyright Autentia 2010. All rights reserved.
//
#import < UIKit/UIKit.h>
@interface PintadoriphoneViewController : UIViewController {
}
// método común a pulsar cualquiera de los botones
-(IBAction)botonPulsado:(UIBarButtonItem *)sender;
@end
Y también el .m

Esto es un poco chapuzas porque hay más modos de hacerlo, como usar otras propiedades como el tag … pero así veis cómo se comparan cadenas.

//
// PintadoriphoneViewController.m
// Pintadoriphone
//
// Created by rcanales on 13/02/10.
// Copyright Autentia 2010. All rights reserved.
//
#import "PintadoriphoneViewController.h"
@implementation PintadoriphoneViewController
// al pulsar los botones modificamos los atributos de la vista
- (IBAction)botonPulsado:(UIBarButtonItem *)sender {
// Asociamos la vista a nuestra vista (un casting explicito no vendría mal comprobando el tipo)
CAreaPintadoView* vista = [self view];
// interesante ver como se comparan dos cadenas ... muy al estilo de Java
if([[sender title] isEqualToString:@"Rect"] == TRUE)
{
NSLog(@"Pues es un rec");
vista.tipo = 0;
}
else if([sender.title isEqualToString:@"Elipse"] == TRUE)
{
vista.tipo = 1;
NSLog(@"Pues es una elipse");
}
}
Yo ahora cerraría el interfaz builder y lo volvería a abrir para que coja la acción. Que no se os olvide que hay que asociar ahora los botones a la acción.
Con control pulsado o con el botón derecho elegimos el ratón y lo arrastramos a File´s Owner que nos mostrará las acciones disponibles.
Repetimos el proceso para los dos botones.

Incorporar el comportamiento de pintado
Ahora en la vista vamos a apoyarnos en un array de tipo NSMutableArray que nos permite añadir objetos dinámicamente sin clave.Cuando el usuario pulse la pantalla nos quedaremos con la primera coordenada y la guardaremos en ítem e ítem.
Al dejar de tocar la pantalla cogeremos las coordenadas y restando a las anteriores calcularemos en ancho y alto.

// CAreaPintadoView.h
// Pintadoriphone
//
// Created by rcanales on 13/02/10.
// Copyright 2010 Autentia. All rights reserved.
//
#import < UIKit/UIKit.h>
// Clase base que asociamos al nib como vista
@interface CAreaPintadoView : UIView {
@private
NSMutableArray* arrayObjetos; // array de objetos a pintar
NSInteger xtem; // variables auxiliares para boton pulsado
NSInteger ytem;
NSInteger anchotem; // variables auxiliares para boton soltado
NSInteger altotem;
@public
NSInteger tipo; // tipo
}
@property NSInteger tipo;
@end
Probamos cómo lo llevamos hasta ahora y en la ventana de build verificamos si hay algún error … a mi me falta un .h que añado.


Aprender a depurar
A estas alturas deberíamos ya saber usar el depurador. Esto sí que es realmente intuitivo y parecido a otros lenguajes.En el editor de código podemos pinchar a la izquierda (el área gris más gorda) para marcar un punto de interrupción.




Para desactivar los puntos de parada o breakpoints en xcode podemos quitarlos arrastrando las flechitas fuera o a través del menú.
Pintando en la pantalla
La clave del código de pintado está en CAreaPintadoView

Luego metemos el objeto en un array que recorreremos cada vez que sea necesario pintar.
Realmente esto no se haría así porque siempre es necesario usar doble-buffers: creas un contexto de tipo imagen que representa la pantalla, pintas en la imagen y luego vuelcas solo la imagen en la pantalla. Así es mucho más eficiente y se crean menos objetos.
//
// CAreaPintadoView.m
// Pintadoriphone
//
// Created by rcanales on 13/02/10.
// Copyright 2010 Autentia. All rights reserved.
//
#import "CAreaPintadoView.h"
#import "CoreGraphics/CoreGraphics.h"
#import "CObjetoGrafico.h"
#import "CRectangulo.h"
#import "CElipse.h"
// clase donde imlementamos todo el código particular de pintado
@implementation CAreaPintadoView
@synthesize tipo; // nos ivocarán desde clases externas
// Metodo invocado cuando se carga desde un nib -initWithCoder:
- (id)initWithCoder:(NSCoder*)coder {
[super initWithCoder:coder]; // no olvidar invocar al padre para que se pinte el nib
NSLog(@"Cargamos desde nib, buen momento para inicializar cosas");
arrayObjetos = [[NSMutableArray alloc] init ]; // inicializamos el array
tipo = 0; // ponemos un tipo 0 por defecto
return self;
}
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
// Initialization code
}
NSLog(@"Inicializamos el frame");
return self;
}
// cojemos la pulsación inicial
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(@"Pulsado");
CGPoint primerPunto;
for (UITouch *touch in touches) {
primerPunto = [touch locationInView:self]; // recuperamos coordenas locales
break; // ignoramos resto de touches
}
xtem = primerPunto.x; // Asociamos las auxiliares
ytem = primerPunto.y;
}
// de momento no nos interesa el movimiento
-(void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
// NSLog(@"Movido");
}
// al levantar los dedos
-(void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
CObjetoGrafico* temporal = nil; // creamos una objeto de la clase base
CGPoint ultimoPunto; // recuperamos coordenas
for (UITouch *touch in touches) { // ete bloque es igual que al pulsar pero no refactorizamos aposta
ultimoPunto = [touch locationInView:self];
break;
}
anchotem = ultimoPunto.x - xtem; // calculamos ancho y alto
altotem = ultimoPunto.y - ytem;
if( tipo == 0) // en función del tipo creamos un objeto distinto para usar polimorfismo
{
NSLog(@"Creamos rect");
temporal = [[CRectangulo alloc] initConValores:xtem y:ytem ancho:anchotem alto:altotem];
}
else if (tipo == 1)
{
NSLog(@"Creamos elipse");
temporal = [[CElipse alloc] initConValores:xtem y:ytem ancho:anchotem alto:altotem];
}
else {
NSLog(@"Error al elegir el tipo objeto");
return;
}
NSLog(@"Soltado");
[arrayObjetos addObject:temporal]; // añadimos elemento al array
// indicamos que hay que refrescar la vista
[self setNeedsDisplay]; // ojo que no lleva el :YES como aparece en algunos sitios de la doc
}
- (void)drawRect:(CGRect)rect { // código invocado al pintar
NSLog(@"Pintamos --En el CAreaPintado");
CGContextRef contexto = UIGraphicsGetCurrentContext();
// Pintar los objetos delegando en la función polimorfica
for ( CObjetoGrafico *elemento in arrayObjetos) { // usamos las enumeraciones rápidas
[elemento traza]; // mostramos los datos en la consola de depuración
[elemento pintate:contexto]; // pasamos contexto
}
}
// como hemos hecho alloc debemos limpiar la memoria. Nunca se invoca dealloc directamente
- (void)dealloc {
[super dealloc];
NSLog(@"Borramos memoria");
// no se nos olvide limpiar los objetos del array
for ( CObjetoGrafico *elemento in arrayObjetos) { // recorremos el array y pintamos
[elemento release];
}
[arrayObjetos release]; // limpiamos el array en si mismo.
}
@end
Ahora añadimos el código de pintado en la clase CObjetoGrafico

// método base que pinta un rectangulo por defecto
-(void) pintate:(CGContextRef *) contexto
{
NSLog(@"Pintamos un rectangulo por defecto");
CGContextSaveGState(contexto); // guardardamos el contexto
CGContextSetRGBFillColor(contexto, 255.0, 0.0,0.0,1.0); // ponemos el color rojo por defecto
CGRect rec = CGRectMake(x, y, ancho, alto); // inicializamos el rectangulo
CGContextFillRect(contexto,rec); // pintamos el rectangulo
CGContextRestoreGState(contexto); // recuperamos el contexto
// para saber si una clase conforma un protocolo conformsToProtocol:@protocol(clase)
}

Hacer una foto (snapshot) del código actual
Hemos llegado a un punto muy estable de código y sería una pena entrar en una espiral de violencia y perder fuentes. Siempre es conveniente usar un repositorio tipo CVS, subversion u otras opciones de pago.A falta de uno, podemos hacerlo localmente creando un SnapShot.




Código en función polimorfa para pintar elipse
Ahora vamos a pintar las elipses

Creamos una función auxiliar
//
// CElipse.h
// Pintadoriphone
//
// Created by rcanales on 13/02/10.
// Copyright 2010 Autentia. All rights reserved.
//
#import < Foundation/Foundation.h>
#import "CObjetoGrafico.h"
@interface CElipse : CObjetoGrafico { // no necesitamos definir los métodos a redefinir
}
-(CGPathRef)creaCirculo; // método auxiliar de instancia para crear la mascara de un circulo
@end
Realmente simple vamos a usar la misma función para pintar un área rectangular pero la vamos a alterar con patrones de
transformación.
// CElipse.m
// Pintadoriphone
//
// Created by rcanales on 13/02/10.
// Copyright 2010 Autentia. All rights reserved.
//
#import "CElipse.h"
#import "CoreGraphics/CoreGraphics.h"
@implementation CElipse
-(void) pintate:(CGContextRef *) contexto
{
CGContextSaveGState(contexto); // Guardamos los atributos del contexto
CGRect rec = CGRectMake(x, y, ancho, alto); // creamos un rectangulo
CGContextSetRGBFillColor(contexto, 0.0, 255.0 , 0.0 , 0.5); // Cambiamos el color del contexto
// la elipse es semitransparente
NSLog(@"Pintamos un rectangulo por defecto");
CGContextAddPath(contexto, [self creaCirculo]); // añadimos el modificador de elipse
CGContextClip(contexto); // definimos el área de pitando.
// si no restaurammos el contexto no podríamos pintar fuera
CGContextFillRect(contexto,rec); // pintamos un rectangulo con la máscara alterada
CGContextRestoreGState(contexto); // restauramos el estado del contexto de nuestra vista
// para saber si una clase conforma un protocolo conformsToProtocol:@protocol(clase)
}
-(CGPathRef) creaCirculo
{
CGMutablePathRef circulo = NULL; // recordar incluir los ficheros de cabecera
CGRect rec = CGRectMake(x, y, ancho, alto);
circulo = CGPathCreateMutable();
CGPathAddEllipseInRect(circulo, NULL, rec);
return circulo;
}
@end
Y ya tenemos nuestro programa completo con cuadrados y circulitos pintamos un osito...

Indentar el código
Si habéis copiado o pegado código es bueno indentarlo.


Observar comportamiento de la memoria
Por último vamos a ver la herramienta para comprobar lagunas de memoria pero ésto tenemos todavía que tratarlo con cariño.

Parece que el problema no es nuestro.

Si os fijáis, a la izquierda, he elegido Allocation Lifespan/ Created & Still Living. Es decir, quiero ver el número de objetos vivos.
Si voy haciendo cosas y se me dispara … malo.




Francamente me encanta (no había estado tan picado desde hace tiempo) . Espero poder compartir con vosotros este nuevo hobby que seguro que tiene miles de salidas en desarrollos empresariales.
A continuación puedes evaluarlo:
Fecha publicación: 2011-01-02-23:27:49
Autor: Pipo
Fecha publicación: 2010-02-16-13:33:38
Autor: rcanales
De todos modos yo procuro mantenerme neutral. Es el cliente el que elige y yo quiero tener los conocimientos y herramientas para dar servicio (dentro de nuestros gustos). De todos modos mira la ventaja competitiva... todo el mundo tiene un PC... poca gente en España tiene un Mac. Si aprendes a programar un MAC, la competencia a priori parece menor (y más si hay que usar Objetive-C y no Java)
También decirte que estamos en paralelo con Android ... así podemos comparar.
Fecha publicación: 2010-02-16-13:02:24
Autor: joxeka
esta muy bien el desarrollo para Iphone/IPod, pero me parece algo intolerable que para poder desarrollar para este dispositivo, las mejores herramientas sólo estén disponibles para Mac, y no para PC. Yo en mi caso, me voy a decantar por Android, porque aunque esté mas verde, no necesito gastarme un pastizal para desarrollar en el.
Un saludo












Esta muy bueno el tutorial, me gustaria poder revisar el anterior que hiciste pero no aparece el tutorial, ademas seria bueno para aprender el merge que no lo discutes en este tuto
un saludo gracias