Está en la página 1de 242

Repaso 1

QUE NECESITO PARA HACER UNA APP IOS

Hardware

Software

Conocimiento

Ideas

Tiempo

HARDWARE NECESARIO

Mínimo

Recomendado

SOFTWARE NECESARIO

Mínimo
SDK PIXELMATOR SMULTRON

Recomendado
SDK PHOTOSHOP DREAMWEAVER SMARTSVN NAVICAT

0 GLSL HTML5 Javascript CSS PHP SQLite MySQL .5/2.CONOCIMIENTO NECESARIO PARA HACER APPS Diseño gráfico y de interfaces Objective .C Cocoa Quartz C/C++ OpenGL ES 1.

¿QUE VAMOS A APRENDER EN ESTE CURSO? Objective C Cocoa Quartz HTML5 Javascript CSS OpenGL ES 1.0 GLSL PHP MySQL SQLite .5 OpenGL ES 2.

Daremos un repaso a Objective .1.Vamos a crear una aplicación de ejemplo y repasaremos algunos aspectos básicos .Delegation and Core Location .Gestión de memoria con ARC .Introducción .C .

Por un lado tenemos la presentación. .2. Se emplea para estructurar las interfaces de los programas de una manera en la que separamos tareas diferentes en diferentes capas.Patrones básicos en cocoa: Patrón MVC El modelo-vista-controlador es un patrón arquitectónico. por otro la lógica de aplicación y por otro la información del dominio.

2.Patrones básicos en cocoa: Delegation El mecanismo de delegados consiste en que un clase A implementa los métodos de otra clase B para hacer se cargo del comportamiento de esta. cuando esos métodos son llamados. El objeto A se llama delegado de B .

Debugger .Imágenes de Lanzamiento .Declaración de variables .Aplicación de ejemplo .Ejecución en el simulador .Instruments .Ejecución en el dispositivo .Creando conexiones de variables y objetos de IB .Enlazando objetivos y acciones .Ver diferentes templates de proyectos XCode .3.Cómo creamos las interfaces desde Interface Builder .Declaración de métodos .Iconos .

Gestión de vistas . pudiendo superponer unas a otras y soportando transparencias.4. .Las diferentes vistas se gestionan como diferentes capas.

5.Ciclo de vida Delegate Inicio Inicio {AbrirTitulo()} AbrirTitulo() Controlador Vista Botón {Preferencias()} AbrirPreferencias() Preferencias() Fin .

Cocoa Touch está escrito en Objective-C y es un conjunto de APIs que nos permiten programar las aplicaciones .Objective-C es una extensión del lenguaje C .6.Las aplicaciones iOS se desarrollan utilizando Objective-C y las librerías de Cocoa Touch .Objective-C .

6. . .Evento: Interacción del usuario con la máquina.Objeto: Es una instancia a una clase. //Método -(void) setNumRuedas:(int)n. .Clases Coche int numRuedas.Mensaje: comunicación dirigida a un objeto ordenándole que ejecute uno de sus métodos.Clase: Define una estructura y comportamiento. . .Método: Define un comportamiento . //Atributo -(int) getNumRuedas. .Atributo: Propiedad de una clase.

para ello llamaremos a su método constructor.7. [listaPersonas init]. necesita ser inicializado. Se suele usar mediante llamadas anidadas: NSMutableArray *listaPersonas = [[NSMutableArray alloc] init].Instancias Para crear una instancia de una clase primero debemos pedir memoria NSMutableArray *listaPersonas = [NSMutableArray alloc]. . Pero para ser usado ese objeto.

8. [listaPersonas addObject:@""].Mensajes . .Mensaje : Comunicación dirigida a un objeto para que ejecute uno de sus métodos NSMutableArray *listaPersonas = [[NSMutableArray alloc] init].

addObject:@"David"].Listas y bucles . definida cuando se crea . NSArray tiene un tamaño fijo.Usamos NSArray para crear listas de objetos. addObject:@"Juan"].i<[listaPersonas count].i++) { NSLog(@"Posición %d de la lista es %@". addObject:@"Carlos"]. for (int i=0. como una constante.i. } .[listaPersonas objectAtIndex:i]). podemos añadir y quitar elementos y el compilador gestionará el tamaño en memoria de la lista NSMutableArray [listaPersonas [listaPersonas [listaPersonas *listaPersonas = [[NSMutableArray alloc] init].NSMutableArray es como NSArray pero la gestión de memoria es dinámica.9.

10.NSString . NSString *cadena2 = [[NSString alloc] initWithString:@"Reserva de memoria con reference counting"]. NSString *cadena1 = @"Reserva de memoria implícita". . [cadena2 release].NSString es una clase de Objective-C usada para representar cadenas. NSString *cadena3 = [[[NSString alloc] initWithString:@"Reserva de memoria autorelease"] autorelease].

Heredar de una clase .Todas las clases de Objective-C tiene una superclase.11.Una clase hereda el comportamiento de su superclase . excepto NSObject .

@property (nonatomic. retain) NSString *nombre.Para acceder a las variables de instancia hace falta tener una instancia de esa clase. @end .Variables y métodos de instancia . } @property (nonatomic.12. NSString *dni. @interface Persona : NSObject { NSString *nombre. @property (nonatomic) int edad. retain) NSString *dni. int edad.

copy: se le asigna a la propiedad una copia del objeto pasado al setter.retain: Se realiza la asignación invocando a retain. . y permite que se pueda leer y escribir su contenido. Lectura/escritura: .assign: opción por defecto.readonly: Esta opción indica que la propiedad es solo de lectura. Este tipo se suele utilizar para propiedades de tipos que no son objetos. . .readwrite: opción por defecto en la definición de las propiedades.12. Tipo de asignación: . Se realiza la asignación sin invocar a retain. . Este tipo solo se puede utilizar para objetos.Variables y métodos de instancia Propiedades: @property (atributos) tipo nombre.

setter=nombre: por defecto. . el setter tendrá como nombre al identificador de la propiedad precedido de la palabra set. si no se indica nada.12.atomic: es la opción por defecto y su función es bloquear la asignación para que ningún hilo pueda modificar la propiedad mientras lo está haciendo otro hilo.nonatomic: en esta opción no hay bloqueo. . el getter tendrá el mismo nombre que el identificador de la propiedad. por lo que cualquier hilo puede modificar el valor sin tener que esperar a que termine el otro. . Por ejemplo: setName. Métodos para acceder: .Variables y métodos de instancia Atomicidad: . Por ejemplo: getName. si no se indica nada.getter=nombre: por defecto.

13. Método de la clase: . . nombreArgN:(tipo)paramN.. .Métodos de clase vs métodos de instancia Método estático de la clase: + (tipo) nombre.. + (tipo) nombre:(tipo)param1 .(tipo) nombre:(tipo)param1 ...(tipo) nombre. nombreArgN:(tipo)paramN.

14. //sin alloc Manual alloc +1 retain +1 release -1 NSString *cadena = [[NSString alloc] initWithString:@”Hola”].Gestión de memoria Implícito NSString *cadena = [NSString stringWithString:@”Hola”]. . [cadena release]. [cadena autorelease]. Automático NSString *cadena = [[NSString alloc] initWithString:@”Hola”].

Gestión de memoria con ARC . .15. .A pesar de eso. no nos podemos olvidar de la gestión de memoria por completo.Evitamos tener que liberar memoria de forma manual y el tiempo que perdemos buscando memory leaks.ARC es un paso de pre-compilación que añade retain/ release/autorelease sin tener que añadirlo manualmente.ARC: Automatic Reference Counting . .

Gestión de memoria con ARC .15.

añade automáticamente una línea para liberar ese objeto: [obj release]. .Esto no significa que el contador de referencias desaparezca. sino que es automático.Gestión de memoria con ARC . . .15.Cuando habilitamos ARC nuestro código queda de la siguiente manera: NSObject *obj = [[NSObject alloc] init].Cuando el precompilador considera que el objeto no se va a usar más.

Sin ARC: @property(retain) NSObject *obj. @property(weak) NSObject *obj. Con ARC: @property(strong) NSObject *obj. Especifica que hay una relación de propiedad (fuerte) sobre el objeto de destino.Referencias fuertes Se utiliza el atributo strong.16. .

en ningún caso se producirá un cuelgue del programa dado que el lenguaje permite enviar mensajes a nil. Especifica que hay una relación de no-propiedad (débil) sobre el objeto de destino. Contamos con la ventaja de que si el objeto en cuestión resulta desalojado de la memoria (dealloc).17. entonces el valor de la propiedad se ajustará automáticamente a nil y.Referencias débiles Se utiliza el atributo weak. . por tanto.

Repaso 2 .

1.Cadenas .AppDelegate .Múltiples controladores .Introducción .UIWebView .Propiedades comunes de los objetos .Labels .UIView .Slider .AlertView .UIScrollView .Imágenes .Text Input .MapKit .

! stringByAppendingFormat:label2.Cadenas Componemos la cadena con el texto de los labels y textfields ! ! ! ! ! NSString cadena = cadena = cadena = cadena = *cadena [cadena [cadena [cadena [cadena = [[NSString alloc] initWithString:@"La cadena es : "].! ! .2. stringByAppendingFormat:textField2.text]. stringByAppendingFormat:textField1. stringByAppendingFormat:label1.text].text].text].

Hidden: [objeto setHidden:TRUE].Propiedades comunes de los objetos UIView *objeto = [[UIView alloc] initWithFrame:CGRectMake:(0.0.0. Transparencia: [objeto setAlpha:0.30)]. Tamaño: [objeto setBounds:CGRectMake(0.300)].3. .300.5]... .300.300)]. Posición: [objeto setCenter:CGPointMake(30.

Label Cambiar texto de un UILabel: [label setText:@”Texto deseado”]. .4.

Cambiar imagen de un UIImageView: UIImage *im = [UIImage imageNamed:@”imagen. imagen. [imagen addSubview:self.jpg”].view].0.image=im.100)]. .Imágenes Instanciar UIImageView y añadirlo a la vista UIImageView *imagen = [[UIImageView alloc] initWithFrame:CGRectMake(0.100.5.

0].(void)viewDidLoad { [slider setMinimumValue:1. ! [slider setValue:5.0].value.6. ! NSString *cadena = [NSString stringWithFormat:@"El valor es %d".valor].0].Slider Configuración del slider en la carga de la vista . } Método que se invocará cuando el slider cambie de valor -(IBAction)valueChange:(id)sender{ ! int valor = (int)slider. } . ! [label setText:cadena]. ! [slider setMaximumValue:10. [super viewDidLoad].

7.UISegmentedControl Detectar cambio en un UISegmentedControl.(IBAction)segmentAction:(id)sender { ! switch ([sender selectedSegmentIndex]) ! { ! ! case 0: //Hacer algo para la primera pestaña ! ! ! break. . ! ! case 1: //Hacer algo para la primera pestaña ! ! ! break. ! } } .

. [alert release]. [alert show].8.AlertView Mostramos un AlertView UIAlertView *alert = [[UIAlertView alloc] ! ! ! initWithTitle: @"Titulo" ! ! ! message: @"Texto aquí" ! ! ! delegate: nil ! ! ! cancelButtonTitle:@"OK" ! ! ! otherButtonTitles:nil].

MapKit Debemos añadir los frameworks de MapKit Configuración del mapa en la carga de la vista { ! ! ! ! ! ! } (void)viewDidLoad [mapa [mapa [mapa [mapa setMapType:MKMapTypeHybrid].! [super viewDidLoad].9. setScrollEnabled:YES]. setShowsUserLocation:YES]. setZoomEnabled:YES]. .

Cajas de introducir texto.10. return YES.Para liberar teclado implementar el protocolo UITextFieldDelegate y sobreescribir el método de su delegado . permiten seleccionar diferentes tipos de teclados .Text Input .(BOOL)textFieldShouldReturn:(UITextField *)tf { [tf resignFirstResponder]. } .

UIView .11.Uno de los elementos más usados .Podemos tener vistas dentro de otras vistas de manera jerárquica .Podemos sobreescribir el método drawRect para que la vista pinte algo específico .

Crear un UIImageView y asignarle una imagen. añadirlo al UIScrollView .Sobreescribir los métodos adecuados para que nos permita hacer zoom sobre la imagen .Crearemos una app de tipo Single View Application .Crear un UIScrollView programáticamente y añadirlo a la vista .Implementar UIScrollViewDelegate en nuestro controlador .12.UIScrollView .

iv.bounds. //Asignar propiedades UIImageView [iv setImage:[UIImage imageNamed:@"wallpaper.width. [self. //Instanciar UIImageView iv = [[UIImageView alloc] initWithFrame: CGRectMake(0. .12.view.view.size. [sv release].view addSubview:sv].bounds.Creación y ajuste de elementos //Instanciar ScrollView sv = [[UIScrollView alloc] initWithFrame: CGRectMake(0.jpg"]].0 animated:TRUE]. [sv setMaximumZoomScale:2.size.5]. //Asignar propiedades UIScrollView [sv setContentSize:CGSizeMake(iv.height*2)].self.size.bounds.5].width*2.UIScrollView .size.size. [iv release]. [sv setZoomScale:1.0.0.view.bounds.size.view.self.height)]. self.bounds. [sv setMinimumZoomScale:0. self.bounds. [sv setDelegate:self].height)]. [sv addSubview:iv].width.

12. } .UIScrollView .Devolver vista sobre la que se realizará zoom -(UIView*)viewForZoomingInScrollView:(UIScrollView *)scrollView{ return iv.

! ! //Create an NSURL Object ! NSURL *url = [NSURL URLWithString:urlAddress]. ! ! //URL Request Object ! NSURLRequest *requestObj = [NSURLRequest requestWithURL:url].text. ! ! //Load the request in the UIWebView.UIWebView Cargar web en un UIWebView -(IBAction)irAPagina:(id)sender { ! //Obtain the url ! NSString *urlAddress = textField.13. ! [webView loadRequest:requestObj]. } .

      Recoger pulsación .14. ! ! case 1: ! ! ! //Pestaña2 ! ! ! break.UITabBar Seleccionar tabButton //Seleccionar el primer elemento    [tabBar setSelectedItem:[tabBar.(void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item { ! switch (item. ! }! ! ! } .items objectAtIndex:0]].tag) { ! ! case 0: ! ! ! //Pestaña1 ! ! ! break.

[controlador1 release]. controlador1=nil.15. .view].Múltiples controladores Cargar un controlador y añadirlo a una vista ! ! controlador1=[[Controlador1 alloc] initWithNibName:@"Controlador1" bundle:nil]. Quitar la vista de un controlador y liberarlo [controlador1.view removeFromSuperview]. [vistaControladores addSubview:controlador1.

16. .Para controlar el ciclo de vida utilizaremos el AppDelegate.Desde los controladores podremos acceder al AppDelegate para invocar un método #import “ClaseD.AppDelegate . . [mainDelegate cambiarAVista2].En AppDelegate instanciaremos nuevos controladores y eliminaremos los que no usemos. .h” ClaseD *mainDelegate = (ClaseD*) [[UIApplication sharedApplication] delegate].

Repaso 3 .

Introducción .UINavigationController .UITableView y UITableViewController .UIPopoverController .Vistas Modales .1.Edición de UITableView .

2.UITableView .Podemos trabajar con este elemento haciendo subclassing de UITableViewController o implementando el delegate de UITableViewControllerDelegate en otro controlador .Vamos a crear un Single View Application .Mostrar una lista de los meses en un UITableView .

UITableView . [lista addObject:@"Enero"]. [lista addObject:@"Abril"].Implementar el protocolo UITableViewDelegate en nuestro controlador . [lista addObject:@"Marzo"]. [lista addObject:@"Junio"]. [lista addObject:@"Diciembre"]. [lista addObject:@"Octubre"]. [lista addObject:@"Febrero"]. [lista addObject:@"Noviembre"]. [lista addObject:@"Julio"].Escribir los métodos del delegate . . [lista addObject:@"Mayo"].Rellenar array lista = [[NSMutableArray alloc] initWithCapacity:12]. [lista addObject:@"Septiembre"]. [lista addObject:@"Agosto"].2.

row]]. return cell. } .Métodos para rellenar contenido . if (!cell) { cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"UITableViewCell"] autorelease].UITableView .2.(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"UITableViewCell"]. } .(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [lista count]. } [[cell textLabel] setText:[lista objectAtIndex:indexPath.

} } . [tableView setEditing:YES animated:YES].Método para modo de editar . } else { [sender setTitle:@"Hecho"].2.UITableView . [tableView setEditing:NO animated:YES].(IBAction)toggleEditingMode:(id)sender { if ([tableView isEditing]) { [sender setTitle:@"Editar"].

} //Quitamos elemento de posición from NSString *e1 = [lista objectAtIndex:fromIndexPath.2. [lista removeObjectAtIndex:fromIndexPath.(void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath { if (fromIndexPath. [e1 release].row].row].row].row) { return. //Lo añadimos a posición to [lista insertObject:e1 atIndex:toIndexPath.Método para mover celdas . } .row == toIndexPath. [e1 retain].UITableView .

Método para eliminar celdas .UITableView .(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { if (editingStyle == UITableViewCellEditingStyleDelete) { //Eliminamos el elemento del array [lista removeObjectAtIndex:indexPath. } } .row]. //Y del tableView con una animación [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:YES].2.

2.row]).UITableView . } .Método para cuando seleccionemos una celda -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { NSLog(@"Ha seleccionado el mes %@".[lista objectAtIndex:indexPath.

window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease].(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.3. UIViewController *vc = [[[ViewController alloc] initWithNibName:@"ViewController" bundle:nil] autorelease].window. return YES.rootViewController = [[[UINavigationController alloc] initWithRootViewController:vc] autorelease]. } . // Override point for customization after application launch. [self.Cambiemos algo de código en AppDelegate para añadir un UINavigationController . self.window makeKeyAndVisible].UINavigationController .

UINavigationController .navigationItem. .navigationItem setTitle:@"Meses"].rightBarButtonItem = editar. [editar release].Configurar barra de navegación //Poner título [self. //Añadir botón editar UIBarButtonItem *editar = [[UIBarButtonItem alloc] initWithTitle:@"Editar" style:UIBarButtonItemStylePlain target:self action:@selector(toggleEditingMode:)]. self.3.

que sólo está disponible en estos dispositivos y no en iPhone e iPod Touch .Vamos a hacer un nuevo ejemplo p a r a i Pa d y u s a r e m o s u n UIPopoverController.Añadir otro controlador y programarlo con la vista que queramos mostrar .4.Crear proyecto Single View Application .UIPopoverController .

Crear puntero en la cabecera de nuestro controlador UIPopoverController *mesesPopover. Escribir métodos del delegate UIPopoverController 4.4. Invocar popOver cuando corresponda . 3.UIPopoverController 1. Implementar protocolo UIPopoverControllerDelegate 2.

} .(void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController { [popOver autorelease].Nos permitirá responder al evento de cerrar esta vista modal .4.UIPopoverController .Método del delegate . popOver = nil.

} } .4.Método que lo despliega -(IBAction)verPopover:(id)sender{ if (popOver==nil) { Otro *otro = [[[Otro alloc] initWithNibName:@"Otro" bundle:nil] autorelease]. popOver = nil. } else { [popOver dismissPopoverAnimated:YES].UIPopoverController . [popOver autorelease]. [popOver setDelegate:self]. [popOver presentPopoverFromBarButtonItem:sender permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES]. popOver = [[UIPopoverController alloc] initWithContentViewController:otro].

. .Vistas Modales . elegir un contacto. etc. enviar un mail.5.Las vistas modales son ViewControllers predefinidos que nos proporciona el sistema y que nos permiten hacer cosas habituales como seleccionar una foto.

h> #import <MessageUI/MFMailComposeViewController. Crear mensaje lanzador .h> 3.Vistas Modales .En nuestro ejemplo vamos a añadir un botón nuevo que nos permita enviar un mail 1.framework #import <MessageUI/MessageUI. Añadir el framework MessageUI.5. Implementar el protocolo MFMailComposeViewControllerDelegate 2. Sobreescribir mensajes del delegate implementado 4.

Sobreescribir método del delegate implementado .5. } .Vistas Modales .Nos permitirá responder al evento de cerrar esta vista modal .(void)mailComposeController:(MFMailComposeViewController*)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error {! ! [self dismissModalViewControllerAnimated:YES].

! ! [picker setMessageBody:emailBody isHTML:NO].mailComposeDelegate = self. ! ! ! ! // Rellenar contenido del mail por defecto ! ! NSString *emailBody = @"Introduce aquí tu texto. if (mailClass != nil)!{ ! if ([mailClass canSendMail]) { ! ! MFMailComposeViewController *picker = [[MFMailComposeViewController alloc] init]. ! ! ! ! [self presentModalViewController:picker animated:YES]. ! ! [picker release]. ! ! ! ! // Añadir destinatarios por defecto ! ! NSArray *toRecipients = [NSArray arrayWithObject:@"correo@enviar. ! ! picker..Vistas Modales . ! ! ! ! [picker setSubject:@"Título del mensaje"].com"].5.! ! ! } } .".barStyle = UIBarStyleBlack..Invocar vista modal ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! Class mailClass = (NSClassFromString(@"MFMailComposeViewController")). ! ! picker. ! ! ! ! [picker setToRecipients:toRecipients].navigationBar.

Repaso 4 .

Settings .Notification and Rotation .Camera .Gestures .Transiciones predefinidas entre vistas .Localization .1.Animaciones .Introducción .Multitarea .

como por ejemplo la rotación del dispositivo // Obtener el objeto dispositivo UIDevice *device = [UIDevice currentDevice].Método manejador . // Empezar a monitorizar notificaciones que tengan que ver con la orientación [device beginGeneratingDeviceOrientationNotifications].Notification and Rotation .Usaremos notificaciones para atender a ciertos eventos. [nc addObserver:self selector:@selector(orientationChanged:) name:UIDeviceOrientationDidChangeNotification object:device].2. } . Texto .(void)orientationChanged:(NSNotification *)note { NSLog(@"orientationChanged: %d". [[note object] orientation]). //Añadir notificación y asignarle un método NSNotificationCenter *nc = [NSNotificationCenter defaultCenter].

3.Camera

- Crearemos un programa que tome una foto de la biblioteca y la asigne en un UIImageView sobre la pantalla

3.Camera
- Implementar el protocolo UIImagePickerControllerDelegate - Obtener foto
-(IBAction)tomarFoto:(id)sender{ //Instanciar controlador UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init]; if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { [imagePicker setSourceType:UIImagePickerControllerSourceTypeCamera]; } else { [imagePicker setSourceType:UIImagePickerControllerSourceTypePhotoLibrary]; } //Asignar la vista actual como delegate y presentarlo [imagePicker setDelegate:self]; [self presentModalViewController:imagePicker animated:YES]; [imagePicker release]; }

3.Camera
- Obtener foto seleccionada
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { //Obtener la imagen y asignarla a un UIImageView UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage]; [imagen setImage:image]; [self dismissModalViewControllerAnimated:YES]; }

4.Settings
- Guardar preferencias
NSUserDefaults *standardUserDefaults = [NSUserDefaults standardUserDefaults]; standardUserDefaults setObject:@”Hola” forKey:@"clave"]; [standardUserDefaults synchronize]; !

- Cargar preferencias
NSUserDefaults *standardUserDefaults = [NSUserDefaults standardUserDefaults]; NSString *cadena = [[standardUserDefaults objectForKey:@”clave”] retain];

xib . . text = "Texto del label". title = "Titulo de la vista".Desde la consola usaremos el comando ibtool para generar el fichero de cadenas a partir de nuestro xib ibtool --generate-strings-file ~/Desktop/ViewController. /* Class = "IBUINavigationItem".Añadimos algunos labels y otros elementos a la vista .strings ViewController.title" = "Titulo de la vista".text" = "Texto del label".Localization . */ "10. */ "8. ObjectID = "10". ObjectID = "8".Creamos nuevo proyecto Single View Application .Ese comando genera un fichero con el siguiente contenido: /* Class = "IBUILabel".Después añadimos a la localización de la vista el idioma Español .5.XIBs .

/es.xib ViewController. */ "10.lproj/ViewController. ObjectID = "10".text" = "Label text". title = "Titulo de la vista".Ahora usamos ibtool para crear un nuevo Xib basado en nuestro xib anterior ibtool --strings-file ~/Desktop/ViewController. */ "8.Editamos el fichero para asignar los valores del idioma al que vamos a traducir /* Class = "IBUILabel".Esto nos modifica el XIB cuando ese idioma sea el de por defecto utilizando los valores del fichero de texto .. ObjectID = "8".xib .title" = "Title of the view". text = "Texto del label".XIBs .5. .strings --write .Localization . /* Class = "IBUINavigationItem".

Vamos a crear un label dinámicamente y queremos que esté localizado UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0.100. [self. [label setText: NSLocalizedString(@"Label añadida".5. .0.Desde la consola usaremos el comando $ genstrings ViewController.m .20)].Localization .view addSubview:label].@"El texto de la label que añadimos") ].Código .Para localizar el código de la aplicación utilizaremos la macro NSLocalizedString .

ya que NSLocalizedString lee el fichero Localizable.strings .strings a nuestro proyecto .Añadimos el fichero Localizable.5.Lo modificamos /* El texto de la label que añadimos */ "Label añadida" = "Label added".Código .Ese comando nos genera el fichero Localizable.strings que contiene lo siguiente /* El texto de la label que añadimos */ "Label añadida" = "Label añadida".Si ejecutamos la app en el idioma al que hemos traducido. . .Localization . saldrá localizada.

6.Multitarea .Controlamos la multitarea mediante la implementación de ciertos métodos en el AppDelegate de nuestras aplicaciones .

Podemos definir ciertos comportamiento para los gestos que registra una vista mediante Gesture Recognizers • • • • • • UITapGestureRecognizer .Rotar UISwipeGestureRecognizer .Mover UILongPressGestureRecognizer .7.Gestures .Pulsación larga .Toque UIPinchGestureRecognizer .Arrastrar izquierda o derecha UIPanGestureRecognizer .Zoom UIRotationGestureRecognizer .

[self.view addGestureRecognizer:swipeRight].delegate = self.Gestures . Crear el gesture regognizer y añadirlo a la vista UISwipeGestureRecognizer *swipeRight = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeRightAction:)].La forma de trabajar con gestures es la siguiente: 1. swipeRight.! [swipeRight release]. swipeRight.7. Implementar protocolo UIGestureRecognizerDelegate 2. Crear método que se encarga de recoger el evento cuanto se produce ese gesto sobre la vista -(void)swipeRightAction:(UIPanGestureRecognizer*)gestureRecognizer{ //METER CÓDIGO } .direction = UISwipeGestureRecognizerDirectionRight. 3.

Animaciones . [UIView commitAnimations]. .alpha=0.Crear una interpolación en el valor de las propiedades de un objeto //DEFINIR PROPIEDADES INICIALES imagen. [UIView setAnimationDelegate:self]. !//DEFINIR PROPIEDADES FINALES !imagen. [UIView setAnimationDuration:0.8. [UIView beginAnimations:nil context:NULL].5].alpha=1.

Transiciones predefinidas entre vistas . [primerControlador release].window cache:YES].0]. primerControlador=nil.view removeFromSuperview]. ! ! segundoControlador = [[SegundoControlador alloc] initWithNibName:@"SegundoControlador" bundle:nil].Eliminar primer controlador. instanciar segundo y indicar transición [primerControlador. [self.9. [UIView beginAnimations:nil context:NULL].window addSubview:segundoControlador.view]. . [UIView commitAnimations]. [UIView setAnimationDuration:1. [UIView setAnimationTransition:UIViewAnimationTransitionCurlUp forView:self.

9.Transiciones predefinidas entre vistas Posibles transiciones entre vistas: UIViewAnimationTransitionCurlUp UIViewAnimationTransitionCurlDown UIViewAnimationTransitionFlipFromLeft UIViewAnimationTransitionFlipFromRigth .

19.Touch Events and UIResponder .

Utilizaremos el gesto doble-tap para limpiar la pantalla .Touch Events . . .En este apartado crearemos una vista sobre la que poder pintar usando las características multitouch de los dispositivos.Elementos como UIScrollView permiten hacer eventos multitáctiles pero su comportamiento está predefinido. .1.Usaremos Touch Events cuando queramos un comportamiento específico.

1.Heredando de UIResponder podremos sobreescribir cuatro métodos para capturar los distintos eventos: Uno o varios dedos tocan la pantalla: .Touch Events .(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event.(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event.(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event. Uno o varios dedos son retirados de la pantalla: .(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event. . El sistema interrumpe este movimiento antes de que termine por una llamada u otro evento: . Uno o varios dedos se mueven por la pantalla: .

Creating the TouchTracker Application .2.Podemos tocar y arrastrar sobre la pantalla y arrastrar para dibujar una linea .Con doble-tap limpiamos la pantalla .

xib en interface builder con nuestra clase TouchDrawView .Crear objeto Line de tipo NSObject .Creating the TouchTracker Application .Crear proyecto de tipo Single View Application .Sobreescribir los métodos de dibujado en TouchDrawView .Crear objeto TouchDrawView de tipo NSObject y personalizarlo para que herede de UIView .2.Asociar un objeto de tipo UIView de ViewController.Sobreescribir los métodos de toques en TouchDrawView .

@end Line. CGPoint end.h” @implementation Line @synthesize begin. } @property (nonatomic) CGPoint begin.m #import “Line.h #import <Foundation/Foundation.2.Creating the TouchTracker Application Line.end. @property (nonatomic) CGPoint end.h> @interface Line : NSObject { CGPoint begin. @end .

Creating the TouchTracker Application TouchDrawView.h #import <Foundation/Foundation. } @end .2.h> @interface TouchDrawView : UIView{ NSMutableArray *lines. CGPoint lineInProgressEnd. CGPoint lineInProgressBegin.

if (self) { lines = [[NSMutableArray alloc] init].0). } return self. [super dealloc]. lineInProgressEnd = CGPointMake(0.h” #import “Line. lineInProgressBegin = CGPointMake(0.Creating the TouchTracker Application TouchDrawView.0).m #import “TouchDrawView. } .h” @implementation TouchDrawView -(id)initWithCoder:(NSCoder*)c { self = [super initWithCoder:c].2. [self setMultipleTouchEnabled:FALSE]. } -(void)dealloc { [lines release].

x.(void)drawRect:(CGRect)rect { CGContextRef context = UIGraphicsGetCurrentContext(). [line begin]. for (Line *line in lines) { CGContextMoveToPoint(context. } } . [line end]. CGContextSetLineCap(context.3. CGContextSetLineWidth(context. } // Linea en progreso en rojo [[UIColor redColor] set]. CGContextAddLineToPoint(context.lineInProgressEnd)){ CGContextMoveToPoint(context.x. // Lineas completas en negro [[UIColor blackColor] set]. CGContextStrokePath(context). 10.y).Drawing with TouchDrawView . [line end]. if (!CGPointEqualToPoint(lineInProgressBegin. lineInProgressEnd.x.Sobreescribir el método drawRect para dibujar lo que queramos sobre la vista. kCGLineCapRound). lineInProgressBegin.y).0). CGContextAddLineToPoint(context.y).y). lineInProgressBegin.x. [line begin]. . lineInProgressEnd. CGContextStrokePath(context).

3.Drawing with TouchDrawView .Hacer el método de limpiar . // Redibujar [self setNeedsDisplay]. } .(void)clearAll { // Borrar la lineas [lines removeAllObjects].

// Redibujar [self setNeedsDisplay]. if ([t tapCount] > 1) { [self clearAll].(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ //Capturamos el evento del primer dedo detectado UITouch *t = [touches anyObject]. } else { //Inicializamos el comienzo y el final de la linea lineInProgressBegin = [t locationInView:self].4. lineInProgressEnd = lineInProgressBegin.Turning Touch into Lines . } } .

//Actualizamos el final de la linea lineInProgressEnd = [t locationInView:self]. } .Turning Touch into Lines .(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{ UITouch *t = [touches anyObject]. // Redibujar [self setNeedsDisplay].4.

//Reiniciar linea en progreso lineInProgressBegin = CGPointMake(0.end=lineInProgressEnd.0). } . linea. lineInProgressEnd = CGPointMake(0.(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{ //Creamos un objeto de tipo linea y //le asignamos los valores de nuestra linea en progreso Line *linea = [[Line alloc] init].0).begin=lineInProgressBegin. // Redibujar [self setNeedsDisplay].Turning Touch into Lines . linea. //Añadimos dicho objeto a la lista de lineas [lines addObject:linea].4.

.5. Mandar mensaje explícitamente al siguiente respondedor: [[self nextResponder] touchesBegan:touches withEvent:event].The Responder Chain UIResponder no maneja el evento porque al no estar sobrescritos sus métodos entonces envía el evento al siguiente respondedor.

6.Crear dos botones. [prefs setObject:data forKey:@"Lineas"]. NSData *data = [NSKeyedArchiver archivedDataWithRootObject:lines].Saving and Loading . [prefs synchronize].Para poder utilizar el método archiveDataWithRootObject es necesario que implementemos en nuestro objeto Line algunos métodos -(IBAction)save:(id)sender{ NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults]. } . uno de Save y otro de Load en nuestra vista y asociarlos a dos métodos que implementaremos .

if (data != nil) { NSArray *oldSavedArray = [NSKeyedUnarchiver unarchiveObjectWithData:data]. NSData *data = [prefs objectForKey:@"Lineas"]. if (oldSavedArray != nil) lines = [[NSMutableArray alloc] initWithArray:oldSavedArray].Saving and Loading -(IBAction)load:(id)sender{ if (lines!=nil) { [lines release]. else lines = [[NSMutableArray alloc] init].6. } . } NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults]. } [self setNeedsDisplay].

[coder encodeFloat:begin. } . end = CGPointMake([coder decodeFloatForKey:@"endX"].x forKey:@"endX"].[coder decodeFloatForKey:@"endY"]). if (self != nil) { begin = CGPointMake([coder decodeFloatForKey:@"beginX"].m . { self = [[Line alloc] init].y forKey:@"endY"].y forKey:@"beginY"]. } return self. { [coder encodeFloat:begin.(void)encodeWithCoder:(NSCoder *)coder.6.[coder decodeFloatForKey:@"beginY"]).(id)initWithCoder:(NSCoder *)coder. [coder encodeFloat:end.x forKey:@"beginX"]. [coder encodeFloat:end.Saving and Loading Line. } .

Circles .7.Con doble-tap limpiamos la pantalla .Podemos tocar y arrastrar sobre la pantalla y arrastrar para dibujar una círculo .

2)).7. float origenX = circle. float diametro = radio*2.begin. //Pintar Círculo CGContextFillEllipseInRect(context.y-circle.y. circle.diametro).begin.end.origenY.radio.end.radio.x-circle. CGRect bordes = CGRectMake(origenX. //Calcular cuadrado que lo engloba CGPoint vector = CGPointMake(circle. .x.y).bordes).begin. //Poner Color [[UIColor blackColor] set]. float origenY = circle.x . float radio = sqrt(pow(vector.2) + pow(vector.x.Circles //Establecer contexto CGContextRef context = UIGraphicsGetCurrentContext().diametro.y .begin.

Por ejemplo UIControlEventTouchUpInside. entre ellos UIButton o UISlider.UIControl .UIControl asocia cada posible evento con una constante.UIControl es la superclase de muchos elementos de Cocoa Touch. .8.UIControl sobreescribe los mismos métodos de UIResponder que hemos visto en este apartado . .

8.UIControl .(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { // Referencia al toque que acaba de terminar UITouch *touch = [touches anyObject]. } else { //Enviar un mensaje diferente [self sendActionsForControlEvents:UIControlEventTouchUpOutside]. //Posición del punto en el sistema de coordenadas del control CGPoint touchLocation = [touch locationInView:self].Veamos como UIControl manejaría el evento UIControlEventTouchUpInside . } } . touchLocation)) { //Enviar mensaje a todos los targets registrados para este evento [self sendActionsForControlEvents:UIControlEventTouchUpInside]. // ¿Esta el punto dentro de los márgenes de mi vista? if (CGRectContainsPoint([self bounds].

Core Animation Layer .20.

por lo que su funcionamiento es muy rápido. .Cuando queramos usar Core Animation debemos añadir el framework de QuartzCore .Las animaciones son la marca característica de las interfaces de iOS y Mac OS X . .En este apartado nos centraremos en CALayer . se pinta en pantalla mediante hardware.Hay dos clases con las que Core Animation funciona.0.CAAnimation causa un cambio mediante interpolación en alguna propiedad de un objeto a lo largo de un periodo de tiempo.CALayer es un buffer que contiene una imagen.Introduction . estas son CALayer y CAAnimation .

1.Layers and views
- Una UIView es realmente una abstracción de un objeto visible con el que poder interaccionar - Cada UIView renderiza su contenido en una CALayer que tiene implícitamente - Un CALayer es un buffer que se pinta directamente sobre la pantalla y que sólo tiene que ver con el renderizado y no con la interacción con el usuario - No todas las CALayer son implícitas, ya que podemos crear CALayers explícitamente

2.Creating a CALayer

- Imagen de fondo - Rectángulo rojo que resalta un área

2.Creating a CALayer
- Crear proyecto de tipo Single View Application - Añadir el framework QuartzCore - Incluir la librería <QuartzCore/QuartCore.h> donde vayamos a usar este framework - Añadir imagen a la vista ViewController.xib - Crear el puntero a la CALayer en ViewController.m - Instanciar nuestra CALayer en el método ViewDidLoad de ViewController.m

2.Creating a CALayer
- Instanciaremos la CALayer en el método ViewDidLoad de ViewController.m ViewController.m
//Crear el nuevo objeto de tipo CALayer boxLayer = [[CALayer alloc] init]; //Ajustar el tamaño adecuado [boxLayer setBounds:CGRectMake(0,0,85,85)]; //Ajustar la posición [boxLayer setPosition:CGPointMake(160,100)]; //Crear un UIColor, después convertirlo a CGColorRef y asociarlo al color de fondo UIColor *rojo = [UIColor colorWithRed:1 green:0 blue:0 alpha:0.5]; CGColorRef rojoCG = [rojo CGColor]; [boxLayer setBackgroundColor:rojoCG]; //Hacer que la CALayer se sublayer de la layer de la vista del controlador [[self.view layer] addSublayer:boxLayer];

y obtener la CGImage UIImage *layerImage = [UIImage imageNamed:@"image.3.Cada layer tiene una coordenada Z para ver cual se pinta primero [boxLayer setZPosition:-5]. . //Poner la imagen en la layer [boxLayer setContents:(id)image].jpg"].Una layer es simplemente una imagen . . CGImageRef image = [layerImage CGImage].Su contenido puede ser generado a partir de una imagen o programáticamente //Crear imagen.Layer Content .

view].Modifiquemos el ejemplo anterior para que la capa reaccione a nuestros toques en la pantalla con una animación de la CALayer desplazandose hasta la posición donde hemos tocado . } .Las CALayer tienen propiedades que pueden ser modificadas y cuando se cambian realizan una animación hasta alcanzar su nuevo valor .Implicity Animatable Properties .4.(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *t = [touches anyObject]. CGPoint p = [t locationInView:self. [boxLayer setPosition:p].

5. ! CGRect boundingBox = CGContextGetClipBoundingBox(ctx).Programmatically Generating Content .(void)drawInContext:(CGContextRef)ctx { ! UIImage *layerImage = [UIImage imageNamed:@"image.Necesario invocar al método setNeedsDisplay de CALayer para que se dibuje su contenido . [layerImage CGImage]). ! CGContextDrawImage(ctx.png"]. boundingBox.Para generar el contenido podemos crear un NSObject y cambiar el padre para que herede de CALayer y implementar la rutinas de pintado: @implementation LayerSubclass . } @end .

.También podemos generar el contenido asignando el delegate de nuestra instancia CALayer a otro objeto que implemente las rutinas de pintado . CGContextDrawImage(ctx.[layerImage CGImage]). } .5. [boxLayer setNeedsDisplay].Programmatically Generating Content .(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx{ UIImage *layerImage = [UIImage imageNamed:@"image.Necesario invocar al método setNeedsDisplay de CALayer para que se dibuje su contenido .Añadir después de la instanciación [boxLayer setDelegate:self]. CGRect boundingBox = CGContextGetClipBoundingBox(ctx).jpg"].boundingBox.

Cuando ejecutamos instrucciones de QuarzCore estas realizan acciones de pintado sobre una CALayer en concreto. . verde. estas instrucciones son ejecutadas en ese contexto .Una imagen es una posición de memoria donde se almacena una lista de componentes rojo. Bitmaps.Layers. and Contexts .6.Una CALayer es simplemente una imagen que después se vuelca a pantalla. azul y alpha para cada pixel .

1.(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *t = [touches anyObject].y/480.0. [boxLayer setNeedsDisplay].7.Dynamic Layer Content . UIColor *rojo = [UIColor colorWithRed:1 green:0 blue:0 alpha:cantidadAlpha].Modificar el ejemplo para que dependiendo de la posición y de donde toquemos el rectángulo sea más o menos rojo . } .(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx{ /* 480 . CGColorRef rojoCG = [rojo CGColor].0 y .cantidadAlpha.view]. p = [t locationInView:self. [boxLayer setPosition:p]. } . */ float cantidadAlpha = p. [boxLayer setBackgroundColor:rojoCG].

21.Controlling Animation with CAAnimation .

Animation Objects .Muchas de las propiedades de CALayer como opacidad. . tamaño.Un ejemplo sería “Muevete del punto A al punto B durante 2 segundos” . etc pueden ser animadas por los objetos de animación utilizando una función de interpolación. posición.1.Un objeto de animación es un conjunto de instrucciones que cambian las propiedades de una instancia de CALayer. .

1. la forma de utilizar estos objetos de animación es mediante alguna de las subclases que heredan de CAAnimation .Animation Objects .En la practica.

cada uno con la imagen de un jugador .2.Imagen de fondo .Spinning with CABasicAnimation .3 botones.Cuando pulsemos en cada uno de esos botones la pelota se desplazará hacia el jugador .

xib .Crear un método para la pulsación de cada botón .Añadir el framework QuartzCore .Incluir la librería <QuartzCore/QuartCore.Crear proyecto de tipo Single View Application .h> donde vayamos a usar este framework .Añadir imágenes y botones a la vista ViewController.2.Crear los punteros y hacer los enlaces .Spinning with CABasicAnimation .

Método de uno de los botones -(IBAction)jugador1:(id)sender{ CABasicAnimation *animacion = [CABasicAnimation animationWithKeyPath:@"position"]. jugador1.center. [animacion setToValue:[NSValue valueWithCGPoint:p2]].Spinning with CABasicAnimation .y).2.x. [animacion setFromValue:[NSValue valueWithCGPoint:p1]].center. [[pelota layer] addAnimation:animacion forKey:@"animacion"].0].x.center. CGPoint p1 = CGPointMake(pelota. } .y). pelota. [animacion setDuration:1. CGPoint p2 = CGPointMake(jugador1.center.

En nuestro ejemplo la animación comienza de repente porque se hace una interpolación lineal .Timing functions . Diferentes valores de interpolación son: kCAMediaTimingFunctionLinear kCAMediaTimingFunctionEaseInEaseOut kCAMediaTimingFunctionEaseIn kCAMediaTimingFunctionEaseOut .Podemos cambiar la función de interpolación por otra diferente como por ejemplo una que comience aceleradamente y termine desacelerando CAMediaTimingFunction *tf = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut].3. [animacion setTimingFunction:tf].

} .Asociar nuestro controlador como delegate de la animación [animacion setDelegate:self].4.Animation completion . .Cuando la animación termine se realizará una llamada al método animationDidStop que deberemos sobreescribir para realizar una acción justo después de que la animación haya terminado. .(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag { [pelota setCenter:puntoFinal].

Vamos a extender el ejemplo anterior para que la pelota cambie de tamaño a lo largo del tiempo.Bouncing with a CAKeyframeAnimation . Cada valor será un key frame .5. simulando de esta forma como si subiera en altura .En las animaciones con CAKeyframeAnimation especificaremos diferentes valores a lo largo del tiempo.

//Asociamos los diferentes valores [animacion3 setValues:[NSArray arrayWithObjects: [NSValue valueWithCATransform3D:t1].1.0.0). //Crear los valores por los que pasará el parámetro transform CATransform3D t1 = CATransform3DMakeScale(1. //Ajustamos el tiempo que durará la animación y configuramos su delegado [animacion3 setDuration:2. CATransform3D t3 = CATransform3DMakeScale(1.2. CATransform3D t2 = CATransform3DMakeScale(2.0).0. nil]].5.1.Bouncing with a CAKeyframeAnimation //Crear la animación de tipo key frames CAKeyframeAnimation *animacion3 = [CAKeyframeAnimation animationWithKeyPath:@"transform"].0.1. [NSValue valueWithCATransform3D:t2]. [animacion3 setDelegate:self].0]. .0. //Añadimos la animación [[pelota layer] addAnimation:animacion3 forKey:@"keyAnimation"].1.1.0. [NSValue valueWithCATransform3D:t3].0.0).

mientras tanto su alpha cambia de 0.que los pelotazos empiecen con velocidad y terminen en seco .además su escala cambia a lo largo de 2 segundos los valores 1.1 .Como hemos visto se pueden solapar varias animaciones en un mismo instante de tiempo .More Animation .0 a 1.6.tarde 2 segundos en desplazarse de un jugador a otro .Extender el ejemplo anterior para que la pelota: .2.0 durante 1 segundo .

La capa de presentación contiene los valores interpolados para ese instante de tiempo.La capa de modelo contiene los valores finales de la animación .The Presentation Layer and the Model Layer . position que cambian de valor.7. CGPoint whereIsItWhenAnimationStops = [layer position]. En ese caso la información correcta de la posición la tendrá la capa de presentación. Cuando hay una animación en marcha. .Esto es útil por ejemplo cuando se tienen objetos con una animación en curso y el usuario realiza un toque sobre ellos.Cuando una instancia de la clase CALayer esta siendo animada esta tiene unos parámetros como son opacity. . CGPoint whereIsItNow = [[layer presentationLayer] position]. transform. . ldicha clase tiene dos copias de esos parámetros.

22.Blocks and Categories .

Colorizing TouchDrawView .1.Vamos a utilizar bloques para pintar con diferentes colores .Partiremos de nuestro ejemplo TouchDrawView .

} @property(nonatomic) CGPoint begin. @end .h : #import <Foundation/Foundation. CGPoint end.retain) UIColor *color. UIColor *color.Antes de nada vamos a modificar la clase “line” para que almacene un color Line. @property(nonatomic) CGPoint end. @property(nonatomic.Colorizing TouchDrawView .h> @interface Line : NSObject { CGPoint begin.1.

1.x forKey:@"endX"]. [coder encodeFloat:end. } @end . } return self.h" @implementation Line @synthesize begin.Line. @synthesize color.y forKey:@"beginY"].[coder decodeFloatForKey:@"endY"]). { self = [[Line alloc] init].end.y forKey:@"endY"]. { [coder encodeFloat:begin.(void)encodeWithCoder:(NSCoder *)coder.(id)initWithCoder:(NSCoder *)coder. } -(void)dealloc{ [color release].Colorizing TouchDrawView . begin = CGPointMake([coder decodeFloatForKey:@"beginX"].x forKey:@"beginX"]. end = CGPointMake([coder decodeFloatForKey:@"endX"]. if (self != nil) { color = [UIColor blackColor]. } . [coder encodeFloat:begin.m : #import "Line. [coder encodeFloat:end.[coder decodeFloatForKey:@"beginY"]).

y). [line begin]. [line begin]. CGContextStrokePath(context).x. quedando el bucle de la siguiente forma: for (Line *line in lines) { [[line color] set].En TouchDrawView.y). [line end]. CGContextMoveToPoint(context.Colorizing TouchDrawView .1. . en el método drawRect sustituiremos: [[UIColor blackColor] set]. } .Por la propiedad el color de nuestra linea. [line end]. CGContextAddLineToPoint(context.m.x.

entenderemos que quiere decir esto unas transparencias más adelante.Los bloques capturan el entorno léxico.2.Blocks .También se pueden devolver como el resultado de un mensaje y ejecutar posteriormente .Trozos de código que se pueden guardar en variables o pasar como argumentos . .

El nombre de esa variable la llamamos color . } .Dentro del método usamos el bloque color si fuera una función -(void)transformLineColorsWithBlock:(UIColor* (^)(Line*))color{ for (Line *l in lines) { UIColor *c = color(l). [l setColor:c]. } [self setNeedsDisplay]. .Blocks as variables .Vamos a crear un método que reciba como variable un bloque.3.

3.0. /* Regla de tres 320 . float longitudY = l.0. [self transformLineColorsWithBlock:esquemaDeColor].g */ float r=longitudX/320. } .end.Después crearemos un botón en la interface y lo enlazaremos con un método que llamaremos colorear -(IBAction)colorear:(id)sender{ UIColor *(^esquemaDeColor)(Line*)=^(Line *l) { float longitudX = l.l.x .1. float g=longitudY/480.x. float b=1. }.1.0 longitudY .0 longitudX .l.r 480 .end.y. return [UIColor colorWithRed:r green:g blue:b alpha:1].y .begin.begin.Blocks as variables .0.

aBlock(). }.4.Los bloques capturan el entorno léxico -(void)metodo{ int value = 5. void (^aBlock)() = ^(void){ NSLog(@"valor=%d".Capturing variables . aBlock(). //Imprime "valor=5" value = 10.value). } //Imprime "valor=5" .

Using blocks with other build-in methods .Animación de vistas .Gestores de error (cuando ocurra un error ejecutar bloque) .Notificación (cuando ocurra un evento ejecuta el bloque) .Gestores de finalización (cuando termines de hacer una tarea.Ordenación (el bloque se proporciona como método de comparación) .5. ejecutar bloque) .Podemos usar bloques en las siguientes acciones: .Para multitarea mediante Grand Dispatch Central .

Vamos a ver un ejemplo de uso de bloques en una notificación . }].Añadimos la siguiente notificación al método initWithCoder de TouchDrawView. .Keeping code compact with blocks .m [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]. [[NSNotificationCenter defaultCenter] addObserverForName:UIDeviceOrientationDidChangeNotification object:nil queue:nil usingBlock: ^(NSNotification *note) { [self transformLineColorsWithBlock: ^(Line *l) { return [UIColor redColor]. }].En nuestro ejemplo vamos a hacer que cuando el dispositivo rote todas las lineas se pongan rojas .6.

@end .7.h : #import <Foundation/Foundation.En nuestro ejemplo crearemos una clase de tipo NSObject y la llamaremos “UIColor+Inverse” UIColor+Inverse.Categories .h> @interface UIColor (Inverse) -(UIColor*)colorInvertido.h> #import <UIKit/UIKit.Crear una categoría consiste en coger una clase existente y añadirle métodos que después podrán ser utilizables sin tener que hacer subclassing .

UIColor *colorInvertido = [UIColor colorWithRed:r green:g blue:b alpha:1. return colorInvertido.components[0]. } @end .7.components[1]. float r = 1. float g = 1.Categories UIColor+Inverse.0 .0 .components[2].0]. const CGFloat *components = CGColorGetComponents(cgCLR).0 .m : #import "UIColor+Inverse. float b = 1.h" @implementation UIColor (Inverse) -(UIColor*)colorInvertido{ CGColorRef cgCLR = [self CGColor].

Ya podemos usar el método colorInvertido con cualquier instancia de UIColor . } [self setNeedsDisplay]. [l setColor:[c colorInvertido]]. } .Modifiquemos el ejemplo anterior.7.Categories . para que el método transformLineColorsWithBlock invierta el color devuelto por el bloque colorear antes de asignarlo al color de la linea -(void)transformLineColorsWithBlock:(UIColor* (^)(Line*))color{ for (Line *l in lines) { UIColor *c = color(l).m importaremos la cabecera UIColor+Inverse. en TouchDrawView.Para usarlo en nuestro ejemplo.h .

Los bloques en Cocoa son objetos aunque algo limitados.copy .8.autorelease Por defecto los bloques reservan memoria automáticamente cuando se crean y son destruidos cuando la función que los crea se completa.release .Memory Managment and Blocks . Se pueden enviar los siguientes mensajes a los bloques: . .retain .

. si un bloque se pasa como fragmento de código a usar al terminar una determinada acción nos garantizamos de que todos los objetos que van a ser usados permanecen en memoria hasta que se complete esta acción.Necesario conocerlos métodos predefinidos en su uso con Grand Dispatch Central (GCD) o para pasar como criterio de comparación en una ordenación por ejemplo.Útiles en algunas circunstancias especiales.Pros and Cons of Callback Options .Código más compacto .Contras: . Por ejemplo como los bloques retienen en memoria los objetos que usan durante su ejecución.Difíciles de usar . .Pros: .9.Sintaxis compleja .

23.Web Services and UIWebView .

1.Web Services
- Cualquier navegador de internet utiliza el protocolo HTTP para comunicarse con un servicio web. - La interacción más simple consiste en que el cliente envía una petición sobre una URL específica al servidor y este le responde enviando el contenido de la página que se ha solicitado. - Para interacciones más complejas el navegador puede incluir parámetros en su petición. Estos parámetros son procesados por el servidor para enviar el contenido de una página dinámicamente construida con respecto a esos parámetros. - Usar un servicio web desde una aplicación iOS requiere los siguientes pasos: - Formatear los datos para enviar como XML o JSON - Enviar los datos en una petición HTTP - Recibir la respuesta HTTP - Parsear y procesar el los datos recibidos como XML o JSON

2.Starting the Nerdfeed application

- Lector RSS - Una lista con noticias RSS obtenidas de un servidor. - Tocando sobre cada una de esos elementos podremos ver la noticia

2.Starting the Nerdfeed application

- Crear proyecto de tipo Empty Application - Añadir una nueva clase de tipo NSObject y cambiar su padre para que herede de UITableViewController - Añadir a este fichero métodos requeridos de data source - En AppDelegate.m crear una instancia de ListViewController y asignarla como root view controller de un navigation controller. - Hacer el navigation controller el root view controller de window

2.Starting the Nerdfeed application
- En AppDeletage.m en el método didFinishLaunchingWithOptions añadiremos
//Instanciar controllador ListViewController ListViewController *lvc = [[ListViewController alloc] initWithStyle:UITableViewStylePlain]; [lvc autorelease]; //Instanciar controlador de navegación UINavigationController *masterNav = [[UINavigationController alloc] initWithRootViewController:lvc]; [masterNav autorelease]; //Asignar como rootViewController el control de navegación [[self window] setRootViewController:masterNav];

- Con eso tendremos nuestro UITableView en pantalla, pero está vacío

. Iniciar conexión .3. Crear contenedor de datos .1.2.h NSMutableData *responseData.m : Instanciarlo Antes de usarlo responseData = [[NSMutableData data] retain].Para crear nuestro contenedor de datos: ListViewController. ListViewController. Recoger datos con los métodos delegados .Fetching data from a URL .3.

NSURLRequest *request = [NSURLRequest requestWithURL:baseURL].com/feed/"] retain]. [[[NSURLConnection alloc] initWithRequest:request delegate:self] autorelease].Working with NSURLConnection .Después debemos implementar una serie de métodos en nuestro controlador para que se vayan recibiendo los datos (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error (void)connectionDidFinishLoading:(NSURLConnection *)connection .Iniciaremos la conexión responseData = [[NSMutableData data] retain].Asignamos nuestro controlador como delegate de la conexión . . NSURL *baseURL = [[NSURL URLWithString:@"http://www.atomicflavor.4.

(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{ NSLog(@"No se pudo descargar el XML").(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{ [responseData setLength:0].(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{ [responseData appendData:data].(void)connectionDidFinishLoading:(NSURLConnection *)connection{ ! //Realizar parseo } . } .4. } .Working with NSURLConnection .Métodos a implementar . } .

com/entrevista-a-atomic-flavor-en-baquia-tv/ </link> <comments> http://www.atomicflavor.5. 15 Jun 2011 09:28:48 +0000</pubDate> .atomicflavor.Veamos un fragmento de lo que contiene un fichero XML y lo que vamos a parsear <item> <title>Entrevista a Atomic Flavor en Baquia TV</title> <link> http://www. que es la librería incluida por defecto en el SDK de iOS .Parsing XML .Vamos a usar NSXMLParser.com/entrevista-a-atomic-flavor-en-baquia-tv/ #comments </comments> <pubDate>Wed.

5.Parsing XML
- 1. Implementar el protocolo NSXMLParserDelegate - 2. Declarar cadenas del XML - 3. Sobreescribir los métodos de parseo - 4. Invocar comienzo de parseo

5.Parsing XML
- 1. Declarar cadenas de parseo
NSXMLParser * rssParser; NSMutableData *responseData; NSMutableArray *items;! NSMutableDictionary * item;! NSString * currentElement; NSMutableString NSMutableString NSMutableString NSMutableString * * * * currentTitle; currentDate; currentSummary; currentLink;

5.Parsing XML
- 2. Iniciar parseo
! NSString *str = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding]; ! NSData *data= [str dataUsingEncoding:NSUTF8StringEncoding]; items = [[NSMutableArray alloc] init]; ! ! ! ! ! ! ! rssParser = [[[NSXMLParser alloc] initWithData:data] autorelease]; [rssParser setDelegate:self]; [rssParser setShouldProcessNamespaces:NO]; [rssParser setShouldReportNamespacePrefixes:NO]; [rssParser setShouldResolveExternalEntities:NO]; [rssParser parse];!

5.Parsing XML
- 3. Sobreescribir métodos del delegado NSXMLParserDelegate: - 3.1 Empieza parseo de elemento - 3.2 Encontrada cadena - 3.4 Termina parseo de elemento - 3.5 Terminado de parsear

! ! currentSummary = [[NSMutableString alloc] init].5.3. ! ! currentTitle = [[NSMutableString alloc] init]. ! ! currentLink = [[NSMutableString alloc] init]. ! ! if ([elementName isEqualToString:@"item"]) ! { ! ! item = [[NSMutableDictionary alloc] init].(void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName: (NSString *)qName attributes:(NSDictionary *)attributeDict { ! currentElement = [elementName copy].1 Empieza parseo de elemento . ! ! currentDate = [[NSMutableString alloc] init].Parsing XML . ! } } .

3.2 Encontrada cadena ! ! ! ! ! ! ! } (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string{ if ([currentElement isEqualToString:@"title"]) { ! [currentTitle appendString:string].5. } .Parsing XML . } else if ([currentElement isEqualToString:@"pubDate"]) { ! [currentDate appendString:string]. } else if ([currentElement isEqualToString:@"link"]) { ! [currentLink appendString:string].

! ! [items addObject:[item copy]]. ! } } .5. ! ! [item setObject:currentDate forKey:@"pubDate"].4 Termina parseo de elemento .3.Parsing XML . ! ! [item setObject:currentLink forKey:@"link"].(void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName: (NSString *)qName{ ! if ([elementName isEqualToString:@"item"]) { ! ! [item setObject:currentTitle forKey:@"title"].

5 Terminado de parsear . } . } .3.Parsing XML .5.(void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError { ! NSLog(@"No se pudo parsear el XML").(void)parserDidEndDocument:(NSXMLParser *)parser { ! [[self tableView] reloadData].

} . return cell. if (cell == nil) { cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"UITableViewCell"] autorelease].Parsing XML . } NSMutableDictionary *i = [items objectAtIndex:indexPath.row]. [[cell textLabel] setText:[i objectForKey:@"title"]]. } -(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath{ UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"UITableViewCell"].5.Por último modificamos los métodos de nuestro ListViewController para que muestren la lista de elementos recuperados -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection: (NSInteger)section{ return [items count].

NSURL *baseURL = [[NSURL URLWithString:@"http://www.com/ feed/"] retain]. } .5.atomicflavor.También crearemos un método para que podamos invocar el comienzo del parseo desde AppDelegate -(void)comenzarConexion{ responseData = [[NSMutableData data] retain]. [[[NSURLConnection alloc] initWithRequest:request delegate:self] autorelease].Parsing XML . NSURLRequest *request = [NSURLRequest requestWithURL:baseURL].

pch que es donde importamos las librerías del precompilador #define WSLog NSLog #define OSLog NSLog .6.Cada vez que queramos imprimir información relaccionada con los webservices usaremos WSLog en lugar de NSLog.En este ejercicio veremos como se imprime mucha información en la consola de depuración sobre todo el contenido que recibimos y enviamos .Lo que vamos a hacer es crear diferentes etiquetas para NSLog en nuestro fichero Nerdfeed_Prefix. .A quick tip on logging . Para el resto de información usaremos OSLog.Después cuando queramos que la información de los webservices no se imprima deberemos redefinir la etiqueta en nuestra clase: #define WSLog .

7.Creamos una nueva clase de tipo NSObject y le cambiamos el padre para que herede de UIViewController . @end .UIViewController.h> @interface WebViewController : UIViewController { } @property (nonatomic.h : #import <Foundation/Foundation.readonly) UIWebView *webView.UIWebView .

. [wv release]. [wv setScalesPageToFit:YES].UIViewController. } @end .UIWebView . UIWebView *wv = [[UIWebView alloc] initWithFrame:screenFrame]. } -(UIWebView*)webView { return (UIWebView*)[self view].7.m : #import "WebViewController.h" @implementation WebViewController @synthesize webView. [self setView:wv].(void) loadView{ CGRect screenFrame = [[UIScreen mainScreen] applicationFrame].

@synthesize webViewController.7.Instanciarlo desde el AppDelegate .Crear una propiedad en ListViewController para apuntar a WebViewController WebViewController *webViewController. . @property(nonatomic.retain) WebViewController *webViewController.UIWebView .

//Crear petición con esa URL NSURLRequest *req = [NSURLRequest requestWithURL:url]. cadenaURL = [cadenaURL stringByReplacingOccurrencesOfString:@"\n" withString:@""]. //Poner como título del control de navegación el título de la noticia [[webViewController navigationItem] setTitle:[i objectForKey:@"title"]]. cadenaURL = [cadenaURL substringToIndex:[cadenaURL length]-1]. WSLog(@"[cadena =%@]". //Limpiar cadena de parásitos finales NSString *cadenaURL = [i objectForKey:@"link"].Método para cuando seleccionemos alguna fila de ListViewController -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{ //Desplazar la vista actual a webViewController [[self navigationController] pushViewController:webViewController animated:YES].cadenaURL). WSLog(@"[cadena =%@]". //Crear NSURL NSURL *url = [NSURL URLWithString:cadenaURL].cadenaURL). //Decirle a webviewcontroller que cargue esa petición [[webViewController webView] loadRequest:req].UIWebView .7. //Obtener el elemento NSMutableDictionary *i = [items objectAtIndex:indexPath. } ! ! . cadenaURL = [cadenaURL stringByReplacingOccurrencesOfString:@" " withString:@""].row].

GDataXML .Algunas de las librerías de terceros que podremos encontrar disponibles son: .TinyXML .TouchXML .KissXML .TBXML .En este ejercicio hemos utilizado NSXMLParser que es el parseador disponible por defecto en la api del SDK de iOS.8.libxml2 .En internet podremos encontrar otras librerías de terceros que también podremos usar para parsear fichero XML sustituyendo a NSXMLParser . .NSXMLParser .

9.The Request Body . las cabeceras HTTP y el cuerpo HTTP . .Hay tres partes.Veamos como es en detalle cada una de las peticiones que realiza nuestro programa. la linea de petición.

(void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { // Se ha fallado 2 veces if ([challenge previousFailureCount] > 1) { //Dar mensaje de aviso } else { // Responder NSURLCredential *cred = [[[NSURLCredential alloc] initWithUser:@"user" password:@"pass" persistence:NSURLCredentialPersistenceForSession] autorelease]. hay veces que es necesario identificarse para poder trabajar con dicho servicio.10.Credentials . [[challenge sender] useCredential:cred forAuthenticationChallenge:challenge]. .Cuando se accede a un servicio web. } } .

11. @property(nonatomic.h> @interface CustomCell : UITableViewCell { UILabel *titulo. UILabel *hora. uno para el título y otro para la fecha.retain) UILabel *titulo.h: #import <UIKit/UIKit. CustomCell.More Data .Vamos a crear una clase CustomCell que sea subclase de UTTableViewCell que tenga varios labels. } @property(nonatomic.retain) UILabel *hora. @end .

Método constructor .(id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { self = [super initWithStyle:style reuseIdentifier:reuseIdentifier].11. [hora setBackgroundColor:[UIColor clearColor]]. //Liberamos [titulo release]. [[self contentView] addSubview:hora].More Data . [titulo setBackgroundColor:[UIColor clearColor]]. [hora release]. } . hora = [[UILabel alloc] initWithFrame:CGRectZero]. } return self. if (self) { //Instanciamos los labels titulo = [[UILabel alloc] initWithFrame:CGRectZero]. //Los añadimos a el contenido de la vista [[self contentView] addSubview:titulo].

} . (h/2)). float inset = 5. float w = bounds. float h = bounds. w.size.11.width. CGRect titleFrame = CGRectMake(inset.height. [hora setFrame:hourFrame].Colocación de SubVistas -(void)layoutSubviews{ [super layoutSubviews]. [titulo setFrame:titleFrame].size. CGRect hourFrame = CGRectMake(inset.inset.0.inset+h/2.More Data . CGRect bounds = [[self contentView] bounds]. (h/2)). w.

More Data .m -(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ CustomCell *cell = (CustomCell*)[tableView dequeueReusableCellWithIdentifier:@"CustomCell"].11. [[cell hora] setText:[i objectForKey:@"pubDate"]]. } .Modificar cellForRowAtIndexPath de ListViewController. if (cell == nil) { cell = [[[CustomCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"CustomCell"] autorelease]. } NSMutableDictionary *i = [items objectAtIndex:indexPath. return cell.row]. [[cell titulo] setText:[i objectForKey:@"title"]].

el UIActivityIndicatorView desaparecerá .Cuando la noticia empiece a cargar en el centro de la pantalla deberemos ver un UIActivityIndicatorView con su animación activada .Añadir UIActivityIndicatorView a nuestra clase WebViewController en el centro de la pantalla .Cuando la noticia se haya terminado la carga.Hay que implementar UIWebViewDelegate en nuestro controlador .Vamos a sobreescribir los métodos de UIWebViewDelegate: .More UIWebView .12.

12. UIWebView *wv = [[UIWebView alloc] initWithFrame:screenFrame]. activityIndicator = [[UIActivityIndicatorView alloc] init].(void) loadView{ CGRect screenFrame = [[UIScreen mainScreen] applicationFrame]. [wv setScalesPageToFit:YES].height/2)].bounds. [activityIndicator setCenter:CGPointMake(self.view.view addSubview:activityIndicator]. [wv setDelegate:self].Cambios sobre loadView . self.bounds.size.view. [self. [activityIndicator setHidesWhenStopped:TRUE]. [activityIndicator stopAnimating].size. [activityIndicator release]. [activityIndicator setColor:[UIColor blackColor]]. [wv release]. [self setView:wv]. [activityIndicator setBackgroundColor:[UIColor clearColor]].width/ 2. } .More UIWebView .

Métodos de UIWebViewDelegate: . } .(void)webViewDidStartLoad:(UIWebView *)webView{ ! [activityIndicator startAnimating]. } .(void)webViewDidFinishLoad:(UIWebView *)webView{ [activityIndicator stopAnimating].More UIWebView .12.

UISplitViewController .24.

El controlador master ocupará poco en la pantalla y será una lista que nos permita elegir que contenido visualizar .El controlador detail ocupará la mayor parte de la pantalla y mostrará en detalle el contenido seleccionado en el master . el master y el detail.1. el iPad tiene una pantalla mucho mayor y puede utilizar clases predefinidas como UISplitViewController que sólo están disponible para iPad.En cambio. . por eso la manera usual de presentar las vistas es mediante un UINavigationController. .Tanto iPhone como iPod Touch tienen un tamaño de pantalla bastante limitado. .Splitting Up Nerdfeed .Para trabajar con UISplitViewController debemos tener dos controladores.

Splitting Up Nerdfeed .Añadiremos un UISplitViewController a nuestro ejemplo Nerdfeed .1.

1.Realizamos cambios en AppDelegate para instanciar el UISplitViewController en caso de estar ejecutando el programa en un iPad .Configuramos los controladores para que soporten cambios a orientación horizontal si el programa se esta ejecutando en un iPad .Splitting Up Nerdfeed .Partimos de el proyecto anterior .

[lvc autorelease]..window. . // Override point for customization after application launch. [lvc setWebViewController:wvc].m .window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease].Splitting Up Nerdfeed . //Instanciar controlador de navegacion UINavigationController *masterNav = [[UINavigationController alloc] initWithRootViewController:lvc].Modificamos el método didFinishLaunchingWithOptions del fichero AppDelegate.(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions: (NSDictionary *)launchOptions { self. WebViewController *wvc = [[[WebViewController alloc] init] autorelease].backgroundColor = [UIColor whiteColor]. //Instanciar controllador ListViewController ListViewController *lvc = [[ListViewController alloc] initWithStyle:UITableViewStylePlain].1. self.. . [masterNav autorelease].

window makeKeyAndVisible].. [self.. [svc setDelegate:wvc]. [detailNav autorelease]. UISplitViewController *svc = [[[UISplitViewController alloc] init] autorelease]. return YES. if ([[UIDevice currentDevice] userInterfaceIdiom] ==UIUserInterfaceIdiomPad){ //El detalle debe ir en un navigation controller UINavigationController *detailNav = [[UINavigationController alloc] initWithRootViewController:wvc]. } . nil]. [svc setViewControllers:vcs]. NSArray *vcs = [NSArray arrayWithObjects:masterNav.Splitting Up Nerdfeed . [[self window] setRootViewController:svc]. } [lvc comenzarConexion]. } else { //Asignar como rootViewController el control de navegación [[self window] setRootViewController:masterNav]..detailNav.1.

Solo se realizará el cambio a horizontal en iPad . } } . } else { return interfaceOrientation == UIInterfaceOrientationPortrait.Añadir el método shouldAutorotateToInterfaceOrientation a ListViewController.m para que la app soporte orientación horizontal .1.(BOOL)shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)interfaceOrientation { if ([[UIDevice currentDevice] userInterfaceIdiom] ==UIUserInterfaceIdiomPad){ return YES.m y WebViewController.Splitting Up Nerdfeed .

podremos seguir indicando a este controlador la página que debe cargar .2.En el caso de estar disponible un splitViewController tendremos quemodificar la forma de presentar el contenido cuando se seleccione un elemento de la lista.Master-Detail Communication .Como desde ListViewController tenemos un puntero hacia WebViewController. Para ello realizaremos el siguiente cambio en didSelectRowAtIndexPath: if (![self splitViewController]) { [[self navigationController] pushViewController:webViewController animated:YES].Por lo general en el master siempre tendremos un puntero hacia el detalle para modificar el contenido . } .

(void)splitViewController:(UISplitViewController*)svc willShowViewController:(UIViewController *)aViewController invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem { self.Displaying the Master View Controller in Portrait Mode .navigationItem.navigationItem.leftBarButtonItem = barButtonItem. } .leftBarButtonItem = nil.Hacemos que nuestro WebViewController implemente UISplitViewControllerDelegate y sobreescribimos los siguientes métodos .3. } .(void)splitViewController:(UISplitViewController*)svc willHideViewController:(UIViewController *)aViewController withBarButtonItem:(UIBarButtonItem*)barButtonItem forPopoverController:(UIPopoverController*)pc { [barButtonItem setTitle:@"Lista"]. self.

Universalizing Nerdfeed .4. .Ahora podemos probar con los diferentes simuladores de iPhone o iPad para ver como la aplicación se comporta de forma diferente.

Media Playback and Background Execution .25.

1.También aprenderemos más sobre multitarea y ejecución en segundo plano .Creating the MediaPlayer Application .Muchas aplicaciones necesitan reproducir audio o video .En este tema aprenderemos a utilizar la forma mas común de reproducir contenido de vídeo y audio utilizando las rutinas que nos ofrece la SDK de iOS .

Aplicación multimedia .1.Reproducir fichero de video .Reproducir fichero de audio .Creating the MediaPlayer Application .Reproducir un sonido del sistema corto .

1.Creamos 3 botones en ViewController.Crear proyecto de tipo Single View Application .Creating the MediaPlayer Application .Programamos el método para cada uno de los botones .xib y sus correspondientes métodos que enlazaremos a estos botones .

System Sounds . WAV o AIFF .caf .Ser de uno de los siguientes tipos: CAF.Los llamados sonidos del sistema son sonidos cortos sin comprimir que se utilizan típicamente en las interfaces o cuando el usuario realiza alguna acción .Menores a 30 segundos de duración .El framework Auditoolbox nos permite registrar una serie de sonidos en el servidor de sonidos del sistema.mp3 sound. Estos sonidos deben ser: .Estar empaquetados en formato PCM o IMA4 .2.Como pasar de mp3 a caf : afconvert -f caff -d LEI16@44100 -c 1 sound.

-(IBAction)reproducirVideo:(id)sender.h> @interface ViewController : UIViewController { SystemSoundID sonidoCorto.System Sounds .2.h : #import <UIKit/UIKit.Importar framework de AudioToolbox .h> #import <AudioToolbox/AudioToolbox. @end . -(IBAction)reproducirSonidoCortoSistema:(id)sender. } -(IBAction)reproducirAudio:(id)sender.Añadir objeto de tipo SystemSoundID ViewController.

3.Cargamos el sonido en viewDidLoad de ViewController. } . AudioServicesCreateSystemSoundID((CFURLRef)soundURL.m //Puntero a su ruta NSString *soundPath = [[NSBundle mainBundle] pathForResource:@"sound" ofType:@"caf"]. if (soundPath) { //Creamos un NSURL y lo cargamos en sonidoCorto NSURL *soundURL = [NSURL fileURLWithPath:soundPath].Registering system sounds . &sonidoCorto).

} .Para reproducir el sonido y que vibre el dispositivo -(IBAction)reproducirSonidoCortoSistema:(id)sender{ AudioServicesPlaySystemSound(sonidoCorto).Descargar sonido AudioServicesDisposeSystemSoundID(sonidoCorto). . } .4.Para reproducir el sonido -(IBAction)reproducirSonidoCortoSistema:(id)sender{ AudioServicesPlaySystemSound(sonidoCorto).Playing system sounds . AudioServicesPlayAlertSound(sonidoCorto).

5.Compressed Audio Files .Para reproducir sonidos comprimidos de más de 30 segundos usaremos AVAudioPlayer .Primero importamos el framework AVFoundation y creamos un puntero de tipo AVAudioPlayer #import <UIKit/UIKit. -(IBAction)reproducirVideo:(id)sender. -(IBAction)reproducirSonidoCortoSistema:(id)sender. @end .h> @interface ViewController : UIViewController { SystemSoundID sonidoCorto. } -(IBAction)reproducirAudio:(id)sender. AVAudioPlayer *musica.h> #import <AVFoundation/AVFoundation.h> #import <AudioToolbox/AudioToolbox.

if (musicPath) { //Creamos un NSURL y lo cargamos en musica NSURL *musicURL = [NSURL fileURLWithPath:musicPath]. musica = [[AVAudioPlayer alloc] initWithContentsOfURL:musicURL error:nil].5. [musica setDelegate:self]. } .m NSString *musicPath = [[NSBundle mainBundle] pathForResource:@"music" ofType:@"mp3"].Cargamos la canción en viewDidLoad de ViewController.Compressed Audio Files .Implementamos en ViewController el delegado AVAudioPlayerDelegate para poder capturar eventos .

} } . [sender setTitle:@"Reproducir música" forState:UIControlStateNormal].5. [sender setTitle:@"Parar música" forState:UIControlStateNormal].Compressed Audio Files .Reproducimos la canción -(IBAction)reproducirAudio:(id)sender{ if ([musica isPlaying]) { [musica stop]. } else { [musica play].

} .Si queremos hacer cosas más avanzadas con AVAudioPlayer sobreescribimos los métodos de su delegado -(void)audioPlayerEndInterruption:(AVAudioPlayer *)player { [musica play].5.Compressed Audio Files . } -(void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag { NSLog(@"Reproducción terminada").

También podemos utilizar programas de terceros que nos permitan realizar este tipo de conversiones .H264 (Baseline Profile Level 3.MPEG-4 Part2 video (Simple Profile) . .Solo reproduce vídeos en dos formatos: .La aplicación de YouTube y de Vídeos utilizan esta misma clase para reproducir vídeos . .MPMovieplayerController es el responsable de reproducir vídeos en iOS.0) .6.Para codificar cualquier video en estos formatos podemos utilizar iTunes. Seleccionando un vídeo y desde el menú avanzado.Playing Movie Files .

h> <AVFoundation/AVFoundation. } -(IBAction)reproducirAudio:(id)sender.Añadir objeto de tipo MPMoviePlayerController ViewController. AVAudioPlayer *musica.h : #import #import #import #import <UIKit/UIKit.Playing Movie Files . -(IBAction)reproducirSonidoCortoSistema:(id)sender.6.h> <MediaPlayer/MediaPlayer.h> <AudioToolbox/AudioToolbox. -(IBAction)reproducirVideo:(id)sender.h> @interface ViewController : UIViewController<AVAudioPlayerDelegate> { SystemSoundID sonidoCorto.Importar framework de MediaPlayer . @end . MPMoviePlayerController *video.

view setFrame:CGRectMake(0. w. float w = self.view.bounds.view addSubview:video.Cargamos el video en viewDidLoad de ViewController.6. } . [self.view]. if (moviePath) { //Creamos un NSURL y lo cargamos en musica NSURL *movieURL = [NSURL fileURLWithPath:moviePath]. [video.width.size.m NSString *moviePath = [[NSBundle mainBundle] pathForResource:@"video" ofType:@"m4v"].height/2.bounds. h.size. float h = self. h)]. } .Y lo reproducimos -(IBAction)reproducirVideo:(id)sender{ [video play].Playing Movie Files .view. video = [[MPMoviePlayerController alloc] initWithContentURL:movieURL].

7. .MPMoviePlayerViewController .Si queremos presentar un vídeo en pantalla completa usaremos la clase MPMoviePlayerViewController y después lanzaremos su presentación MPMoviePlayerViewController *playerViewController = [[MPMoviePlayerViewController alloc] initWithContentURL:movieURL]. [self presentMoviePlayerViewControllerAnimated:playerViewController].

video = [[MPMoviePlayerController alloc] initWithContentURL:movieURL].Preloading video . Mas aún si lo cargamos en streaming desde un servidor .Podemos añadir a nuestro objeto de vídeo una notificación apuntando a un método para que sea llamado cuando el vídeo se haya terminado de cargar completamente. if (moviePath) { //Creamos un NSURL y lo cargamos en musica NSURL *movieURL = [NSURL fileURLWithPath:moviePath]. NSString *moviePath = [[NSBundle mainBundle] pathForResource:@"video" ofType:@"m4v"]. } .8.Cuando iniciamos la carga de un vídeo no está disponible de manera inmediata. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(displayPreloadedVideo:) name:MPMoviePlayerLoadStateDidChangeNotification object:video].

view setFrame:CGRectMake(0.Preloading video .height/2.view.view.8. float w = self.width. h. } . w. h)]. float h = self. [video.size.size.bounds.bounds.view addSubview:video.El método que añadimos para atender a esa notificación -(void)displayPreloadedVideo:(NSNotification*)note{ [self.view].

9.plist de nuestro proyecto el correspondiente modo de background.Una aplicación puede reproducir audio incluso si no es la aplicación activa.Background Processes . . Par a ello añadimos un campo más con el valor UIBackgroundModes. La clave que corresponde con la reproducción de audio en segundo plano es App plays audio .Para configurar este tipo de proceso de segundo plano debemos añadir a el fichero Info.

La categoría que permite reproducir audio en segundo plano es AVAudioSessionCategoryPlayback NSString *musicPath = [[NSBundle mainBundle] pathForResource:@"music" ofType:@"mp3"]. } .Background Processes . musica = [[AVAudioPlayer alloc] initWithContentsOfURL:musicURL error:nil].Para que la aplicación pueda reproducir audio en segundo plano deberemos cambiar la categoría de AVAudioSession. . if (musicPath) { //Creamos un NSURL y lo cargamos en musica NSURL *musicURL = [NSURL fileURLWithPath:musicPath]. [musica setDelegate:self].9. //Cambiamos la categoria para reproducir audio en segundo plano [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil].

el usuario no puede verlas .No actualices tus vistas.Guidelines for background execution .10.Hay algunas reglas que Apple nos dice que debemos de seguir en el caso de las aplicaciones que tienen ejecución en segundo plano. Algunas de estas son: . el sistema operativo termina el proceso por la elevada carga .Libera recursos cuando se produzca una llamada de low-memory warning .No uses OpenGL ES.

Además de la reproducción de audio en segundo plano hay otros dos modos de ejecución: .Other forms of background execution .VOIP (Voice over internet protocol) .Registers for location updates .11.

Usaremos AudioToolbox y CoreAudio para los sonidos .Usaremos CoreVideo para el vídeo .En este tema hemos visto la forma más sencilla de reproducir vídeo y audio utilizando la API de alto nivel . efectos o lo que necesitemos deberemos utilizar las APIs de bajo nivel . aplicar filtros.Low-level APIs .12.Si queremos hacer cosas más avanzadas como recodificar.

Un botón play que reproduzca el último archivo grabado .13.Podemos grabar audio utilizando la clase AVAudioRecorder .Vamos a modificar nuestro ejemplo para añadir: .Un botón grabar que nos permita grabar .Audio Recording .

AVAudioRecorder *grabadoRecorder. bool grabando.13. -(IBAction)grabar:(id)sender. -(IBAction)reproducir:(id)sender. .Audio Recording .Punteros necesarios AVAudioPlayer *grabadoPlayer.

Audio Recording -(IBAction)grabar:(id)sender{ if (!grabando) { //Settings de grabar NSMutableDictionary *recordSettings [recordSettings setObject:[NSNumber [recordSettings setObject:[NSNumber [recordSettings setObject:[NSNumber [recordSettings setObject:[NSNumber [recordSettings setObject:[NSNumber [recordSettings setObject:[NSNumber = [[NSMutableDictionary alloc] initWithCapacity:10]. if (grabadoRecorder!=nil) { [grabadoRecorder stop]. [sender setTitle:@"Parar grabación" forState:UIControlStateNormal]. } } . NSURL *url = [NSURL fileURLWithPath:recDir]. NSUserDomainMask. //Ponemos la sesion en modo grabación AVAudioSession *audioSession = [AVAudioSession sharedInstance].0] forKey: AVSampleRateKey]. numberWithInt:2] forKey:AVNumberOfChannelsKey]. [grabadoRecorder stop]. @"fichero"]. numberWithInt:16] forKey:AVLinearPCMBitDepthKey]. [grabadoRecorder record]. numberWithInt: kAudioFormatLinearPCM] forKey: AVFormatIDKey].13. [sender setTitle:@"Grabar" forState:UIControlStateNormal]. [audioSession setCategory:AVAudioSessionCategoryRecord error:nil]. YES). numberWithBool:NO] forKey:AVLinearPCMIsFloatKey]. } } else { grabando=false. } grabadoRecorder = [[ AVAudioRecorder alloc] initWithURL:url settings:recordSettings error:nil]. [grabadoRecorder release]. numberWithBool:NO] forKey:AVLinearPCMIsBigEndianKey]. numberWithFloat:44100. NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory. NSString *recDir = [[paths objectAtIndex:0] stringByAppendingFormat:@"/%@". if ([grabadoRecorder prepareToRecord] == YES) { grabando=true.

13. NSURL *url = [NSURL fileURLWithPath:recDir]. if (grabadoPlayer!=nil) { [grabadoPlayer stop].Audio Recording . @"fichero"]. [audioSession setCategory:AVAudioSessionCategoryPlayback error:nil]. NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory. } grabadoPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil]. } . [grabadoPlayer release]. [grabadoPlayer play]. NSUserDomainMask.Método de reproducción -(IBAction)reproducir:(id)sender{ //Ponemos la sesion en modo reproducción AVAudioSession *audioSession = [AVAudioSession sharedInstance]. NSString *recDir = [[paths objectAtIndex:0] stringByAppendingFormat:@"/%@". YES).

Push Notifications and Networking .26.

Push Notifications .Podemos reproducir un sonido . mecanismo para enviar notificaciones a los usuarios que tengan instalada nuestra aplicación .Podemos cambiar el badge de el icono de la aplicación .1.Podemos mostrar un mensaje corto .Push Notifications.

Esquema de Push Notification .Push Notifications .1.

world!".Anatomy of a Push Notification .2. "badge": 2 } } . world!" }."sound":"default"}} . "body": "Hello.Como lo usaremos {"aps":{"alert":"Hello.Una notificación básica { "aps": { "alert": "Hello.Configurando también el botón de abrir la notificación { "aps": { "alert": { "action-loc-key": "Open". "sound": "default" } } . world!".

.Development .Tu servidor necesita firmar las comunicaciones con los servidores APNS mediante un certificado SSL .Production .Provisioning Profiles .Para habilitar push notifications en tus aplicaciones. necesitan ser firmadas con un provisioning profile que esté configurado para push.Hay dos tipos de push server certificates: .3.

Abrir keychain .Solicitar un certificado para una autoridad de certificación . Generating the Certificate Signing Request (CSR) .4.

Generating the Certificate Signing Request (CSR) .4.

4. Generating the Certificate Signing Request (CSR) .

4. Generating the Certificate Signing Request (CSR) .

5. Making App ID and SSL Certificate .

Making App ID and SSL Certificate .5.

Making App ID and SSL Certificate .5.

5. Making App ID and SSL Certificate .

buscamos el certificado y le damos a detalle . el certificado y la clave privada .p12 .Veremos los dos.Seleccionar los dos y exportamos como un fichero.Después abriremos de nuevo KeyChain.Una vez descargado el certificado.5. Making App ID and SSL Certificate .cer hacer doble click y instalarlo .

Making App ID and SSL Certificate .5.

pem -nodes .p12 -out Certificado. Making App ID and SSL Certificate .Convertimos el certificado en un fichero .5.pem openssl pkcs12 -in Certificado.

6. Simple PHP Server .simplepush.Subimos los ficheros: .com/PushNotification/simplepush.php .ck.atomicflavor.php .Tendré acceso desde: http://www.pem generado .

PHP_EOL. ! 'sound' => 'default' ! ).apple. echo 'Connected to APNS' . Simplepush. pack('n'. 'ssl'. 32) . 60. stream_context_set_option($ctx. PHP_EOL). $payload. else ! echo 'Message successfully delivered' . // Send it to the server $result = fwrite($fp. $deviceToken) .php <?php // Put your device token here (without spaces): $deviceToken = '0f744707bebcf74f9b7c25d48e3358945f6aa01da5ddb387462c7eaf61bbad78'.pem'). $passphrase). $ctx). pack('n'.com:2195'. 'Certificado.sandbox. // Build the binary notification $msg = chr(0) . stream_context_set_option($ctx. // Put your alert message here: $message = 'My first push notification!'. 'local_cert'.7.push. if (!$fp) ! exit("Failed to connect: $err $errstr" . . strlen($payload)) . PHP_EOL. // Put your private key's passphrase here: $passphrase = 'curso'. ! $errstr. 'ssl'. STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT. // Close the connection to the server fclose($fp). if (!$result) ! echo 'Message not delivered' . pack('H*'. // Encode the payload as JSON $payload = json_encode($body). strlen($msg)). $err. // Open a connection to the APNS server $fp = stream_socket_client( ! 'ssl://gateway. //////////////////////////////////////////////////////////////////////////////// $ctx = stream_context_create(). // Create the payload body $body['aps'] = array( ! 'alert' => $message. 'passphrase'. $msg. PHP_EOL.

Hacemos que la app se registre para recibir push notifications .   return YES.rootViewController = self.viewController.8. ! [self.(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { ! self.window makeKeyAndVisible].   ! // Let the device know we want to receive push notifications ! [[UIApplication sharedApplication] registerForRemoteNotificationTypes: ! ! (UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert)].Nueva single view application . } . Simple iPhone Client .window.

viewController.window makeKeyAndVisible]. Simple iPhone Client .(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { ! self. ! [self. } .Hacemos que la app se registre para recibir push notifications .   return YES.Nueva single view application .window.   ! // Let the device know we want to receive push notifications ! [[UIApplication sharedApplication] registerForRemoteNotificationTypes: ! ! (UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert)].8.rootViewController = self.

Hacemos que la app se registre para recibir push notifications . } .8.(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { ! self. ! [self. Simple iPhone Client .rootViewController = self.window makeKeyAndVisible].viewController.   return YES.   ! // Para que el dispositivo reciba push notifications ! [[UIApplication sharedApplication] registerForRemoteNotificationTypes: ! ! (UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert)].Nueva single view application .window.

error: %@". } .(void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken { ! NSLog(@"My token is: %@". deviceToken). Simple iPhone Client .(void)application:(UIApplication*)application didFailToRegisterForRemoteNotificationsWithError:(NSError*)error { ! NSLog(@"Failed to get token. } .8. error).Nos hacen falta algunos métodos más .

Vemos el token del dispositivo y modificamos el php del servidor para que la notificación se envíe a ese dispositivo .9.A continuación podremos ejecutar nuestra app en el dispositivo .Ya podemos enviar nuestra primera push notification desde el servidor y recibirla en el dispositivo . Create Provisioning Profile .Ahora vamos a crear el provisioning profile y a instalarlo en el XCode .

0 GLSL PHP MySQL SQLite .5 OpenGL ES 2.¿COMO CONTINUO EL CAMINO PARA CONVERTIRME EN UN PROGRAMADOR NINJA? Objective C Cocoa Quartz HTML5 Javascript CSS OpenGL ES 1.

LIBROS RECOMENDADOS EDITORIAL APRESS .

CONTACTO Miguel José García Corchero miguel@atomicflavor.atomicflavorgames.com twitter: @miguelgarciacor http://www.com http://www.atomicflavor.com .