viernes, 18 de febrero de 2011

Conceptos Fundamentales - Categorías y Extensiones

Categorías

Una categoría permite añadir nuevos métodos a clases ya existentes sin crear subclases. Lo que no se puede es agregar variables de instancia para esa clase.
Para una categoría tenemos dos archivos que son los mismos que para una clase, la interfaz que consiste en un archivo .h y la implementación que consiste en el archivo .m .

La diferencia que existe con una clase es que, las categorías:
1 - Tienen un nombre de una clase ya existente como por ej NSObject
2 - En la interfaz no indicamos de quien heredamos
3 - En la intefaz como en la implementación el nombre de la categoría va entre paréntesis

No se puede usar @sintetize
El nombre del archivo de una categoría es el de la clase seguido de la categoría.

Las categorías son muy útiles cuando se quiere agregar funcionalidad a una clase, pero no queremos reescribirla o no contamos con el código fuente el código fuente de la clase como cuando esta se encuentra en una biblioteca.

Por ejemplo:

Supongamos que tenemos la clase Perro y agregamos la categoría PerroTrucos
//  Perro.h
@interface Perro : NSObject{
}
-(void) ladra;
@end


//  Perro.m
#import "Perro.h"
@implementation Perro
-(void) ladra {
        //Guau guau
}
@end


//  PerroTrucos.h
@interface Perro (Trucos) {
}
-(void) traeLaVarita;
@end


//  Perro.m
#import "PerroTrucos.h"
@implementation Perro (Trucos)
-(void) traeLaVarita {
 //va por la varita
}
@end

Para crear una categoría, se debe declarar con @interface NombreDeClaseExistente (NombreDeCategoriaNueva)
Una vez declarada la categoría se define con @implementation NombreDeClaseExistente (NombreDeCategoriaNueva)

Extensiones
La extensión es un caso especial de categoría a la cual no se le da un nombre entre parentesis, es anonima.
Sus métodos se implementan en la implementación principal de nuestra clase para que puedan definir metodos privados.

jueves, 17 de febrero de 2011

Parte 2 - Tarea 1. Ejemplo aplicación iphone (Calculadora)

Objetivos

1) Bajar e instalar el iOS4 SDK. (esto pueden hacerlo desde  http://developer.apple.com/devcenter/ios/index.action )
2) Crear un nuevo proyecto con Xcode.
3) Definir un modelo, vista y controlador y conectarlos.
4) Usar Interface Builder para crear la interfaz gráfica


Parte 1  

Crear un nuevo proyecto en Xcode

1. Abrimos /Developer/Applications/Xcode
2. En la pantalla que aparece seleccionamos create a new Xcode project



También se puede crear un nuevo proyecto seleccionando New Proyect desde el menú.



3. En el dialogo que aparece hacemos click sobre View based Application



4. En la pantalla que se presenta hacemos lo siguiente:
  • Navegamos hasta el lugar donde queremos mantener los proyectos para este curso
  • En el campo Save as le ponemos el nombre Calculator


    5. hacemos click en Save.



    Ahora podemos ejecutar la aplicación aunque aparecerá la pantalla del simulador del iphone en blanco. Si todo esto funciona bien entonces el SDK se ha instalado correctamente :).

    6. Volvemos a Xcode y podemos ver que hay un arbol de carpetas llamados Grupos y Archivos (Groups & Files). En este lugar es donde serán administrados todos los archivos.




    En la sección Classes se crean automaticamente los archivos .h y .m para las dos clases  CalculatorAppDelegate y CalculatorViewController. Por ahora no hay que preocuparse por CalculatorAppDelegate.

    Parte 2
    Crear una nueva clase para nuestro modelo

    7. Para crear una nueva clase hacemos click en Groups & Files y seleccionamos New File ... desde el  menú.


    8. Hacemos click sobre Objective class (Subclase de NSObject) y después en el botón siguiente. Todos los objectos en Objective-C son subclases de NSObject.

    9. Xcode preguntará por el nombre de la clase. Escribimos CalculatorBrain.m y dejamos chequeado la opción “CalculatorBrain.h” (Also create “CalculatorBrain.h” ) porque queremos ambos archivos, el de cabecera  (.h) y el de implementación (.m) para nuestra clase CalculatorBrain class.
    Nuestro Modelo ya ha sido creado. Vamos a dejar de lado el modelo y volvemos al controlador para escribir algunas declaraciones para las conexiones que necesitamos hacer entre la vista y el controlador.

    Parte 3
    Definir las conexiones desde/hasta el controlador

    Ahora que ya existen las clases de nuestro modelo hay que implementar nuestro controlador.
    Comencemos definiendolo.

    10. Hacer click en CalculatorViewController.h.

    Vamos a ver una pantalla como la siguiente:



    Notar que Xcode ha puesto el #import de la UIKit  que necesitamos y ha hecho que
    CalculatorViewController sea una subclase de UIViewController. Los Controladores son siempre directa o indirectamente subclases de UIViewController.

    Nuestro archivo de cabecera todavía necesita que definamos lo siguiente:
        a. outlets (variables de instancia en nuestro controlador que apuntan a objetos en nuestra vista)
        b. actions (métodos en nuestro controlador que van a ser enviados desde la vista)
        c. Una variable de instancia en nuestro controlador que apunta a nuestro modelo

    11. Vamos a agregar el outlet que permite que nuestra CalculatorViewController hable con UILabel que representa el display en la vista. LLamaremos display a ese outlet.

    @interface CalculatorViewController : UIViewController {
        IBOutlet UILabel *display;
    }
    @end
    
    

    Notar que la palabra clave IBOutlet no hace nada excepto identificar los oulets.


    12. Ahora agregaremos una variable de instancia llamada brain que apunta desde nuestro Controlador hasta nuestro modelo CalculatorBrain. Necesitamos agregar un  #import a CalculatorViewController.h para encontrar la declaración de CalculatorBrain.

    #import <UIKit/UIKit.h>
    #import "CalculatorBrain.h"
    @interface CalculatorViewController : UIViewController {
        IBOutlet UILabel *display;
        CalculatorBrain *brain;
    }
    @end
    
    13. Y finalmente vamos a agregar dos acciones que la vista va a enviar  cuando se presionen los botones.

    @interface CalculatorViewController : UIViewController {
        IBOutlet UILabel *display;
        CalculatorBrain *brain;
    }
    - (IBAction)digitPressed:(UIButton *)sender;
    - (IBAction)operationPressed:(UIButton *)sender;
    @end
    
    
    El único argumento de cada método es un objeto UIButton (el objeto le envia el mensaje a nuestro controlador cuando hace click sobre el mismo).



    14. Compilamos y ejecutamos nuevamente la aplicación para saber si hay algún error. Seguramente hay advertencias porque los métodos no están implementados pero no hay problema con esto ya que son solo advertencias. Si aparece algún error se vera el circulo rojo como se ve en la imagen siguiente:



    haciendo click sobre el error se muestra más información sobre el mismo.


    Ahora utilizaremos la herramienta gráfica para agregar un display algunos botones.


    Parte 4

    Crear la Vista con el Interface Builder

    No necesitamos escribir código sino que solo utilizaremos la herramienta Interface Builder. Cuando creamos un proyecto View-base Xcode automaticamente se crea un template . Este template se llama CalculatorViewController.xib (también denominado archivo nib, algunos lo llaman archivo zib ).

    15. Abrir CalculatorViewController.xib.

    16. Interface Builder tiene tres ventanas principales que contienen objetos o grupos de objetos. Es recomendable seleccionar Hide Others para ver que sucede en el Interface Builder.



    La pantalla principal en el Interface Builder muestra todos los objetos de tu archivo .xib:



    El controlador CalculatorViewController es el File’s Owner . Por ahora ignoremos el objeto First Responder. El objeto View es la vista de más alto nivel, es la supervista de todas las vistas que tendremos en la interface. Todos los objetos UIView tienen una jerarquía en la cual cada uno tiene una superview y subviews. Otra de las ventanas es la librería que contiene todos los elementos necesarios para construir la vista. Por ahora vamos a usar dos UIButton and UILabel.


    Otra de las ventanas es el inspector. El contenido de esta ventana cambia dependiendo que objeto este seleccionado. 

    17. Si hacemos  click  en File’s Owner en la pantalla principal, se debería ver esta pantalla:



    La clase de tu File’s Owner debe ser CalculatorViewController. Lo próximo a realizar es poner algo en la pantalla de esta manera:


    18. Comencemos con el dígito 7, localizamos en nuestra librería un botón Round Rect y simplemente lo arrastramos a la vista.


    19. Modificamos el botón para que tenga 64 pixels de ancho. 

    20. La parte más importante es unir el botón con el CalculatorViewController (el File’s Owner en la pantalla principal) que es quien va a enviar el mensaje digitPressed: cuando hagan click sobre el botón. Para esto hay que hacer click en Ctrl y arrastrar una linea desde el botón hasta el Files Owner.


    A medida que te acercas al File’s Owner, debe rodearlo una caja azul, cuando soltas el mouse debe aparecer una ventanita, selecciono en ella el metodo digitPressed: 


    22. Y ahora que ya tenemos hecha la conexión copiamos y pegamos los botones, todos ellos enviaran digitPressed.

    23. Haciendo doble click sobre cada botón se le puede cambiar el nombre. 

    Ahora vamos a hacer las operaciones de los botones:
    24. Arrastramos a la pantalla un botón.

    25. Mantenga presionado Ctrl y arrastro una lines desde este nuevo boton hasta File’s Owner. Nuevamente va a aparecer la ventanita negra pero en este caso la operación va  a ser operationPressed:.

    26. Copiamos esto 5 veces (necesitaremos * / + - = y sqrt) 
    La interfaz debería verse de esta manera:



    27. Arrastramos una etiqueta (UILabel) desde la librería, hacemos doble click y escribimos 0.

    28. Seleccionamos la etiqueta.

    29. Podes modificar las propiedades de los objetos como quieras como se ve en la siguiente imagen:


    El CalculatorViewController necesita enviar mensajes al outlet para actualizarlo.

    31. Arrastramos desde el File’s Owner to the UILabel.


    32. Cuando soltamos el mouse debe aparecer la siguiente ventana. Selecciona display.


    33. Guardamos el archivo de la Interface Builder y luego vuelvemos al Xcode.

    34. Ejecutamos la aplicación.

    Parte 5
    Implementar el Modelo

    El próximo paso es implementar el modelo CalculatorBrain.

    35. Hacemos click en CalculatorBrain.h en la sección  Groups & Files.


    36. Primero nuestro modelo necesita un operando. Será un puntero flotante, entonces hagamos una variable de instancia que sea double.


    double operand;
    

    37. Ahora agreguemos un método que nos permita setear un operando.

    - (void)setOperand:(double)aDouble; 
    

    38. Y finalmente un método que nos permita realizar una operación.

    @interface CalculatorBrain : NSObject {
      double operand;
    }
    - (void)setOperand:(double)aDouble;
    - (double)performOperation:(NSString *)operation;
    @end
    
    

    39. Copiar la declaración de los dos métodos en CalculatorBrain.h, luego cambiar a CalculatorBrain.m  y pegarlo entre @implementation y @end.

    //
    // CalculatorBrain.m
    // Calculator
    //
    // Copyright Stanford CS193p. All rights reserved.
    #import "CalculatorBrain.h"
    @implementation CalculatorBrain
    - (void)setOperand:(double)aDouble;
    - (double)performOperation:(NSString *)operation;
    @end
    
    
    @implementation CalculatorBrain
    - (void)setOperand:(double)aDouble
    {}
    - (double)performOperation:(NSString *)operation
    {}
    @end
    
    

    40. La implementación de setOperand: es facil. Lo que hacemos es darle el valor que viene como argumento a la variable de instancia.

    - (void)setOperand:(double)aDouble
    {
        operand = aDouble;
    }
    
    

    41. La implementación de  performOperation: también es simlple si el operando lo es, como por ej sqrt.

    - (double)performOperation:(NSString *)operation
    {
        if ([operation isEqual:@"sqrt"])
        {
           operand = sqrt(operand);
        }
    
    return operand;
    }
    


    El envío de mensajes a los objetos se hace entre corchetes. En este caso el mensaje es isEqual:.
    Pensemos en operaciones con 2 operandos. Esto es un poco más dificil.
    Imaginemos un usuario que interactua con la calculadora. El usuario ingresa un número,
    luego una operacion, después otro número y despues presiona otra operación (o el igual) que es cuando espera que aparezca el resultado.
    Si hace 12 + 4 sqrt = el resultado que se espera sera 14 y no 4. Las operaciones de 2 operandos deben ser retrasadas hasta la próxima operación o hasta que se presione el igual.

    42. Volvemos a CalculatorBrain.h y agregamos 2 variables de instancias que necesitaremos para las operaciones de 2 operandos: una variable para la operación que esta esperando ser ejecutada hasta que se presione el otro operando y la otra para la siguiente operación.

    @interface CalculatorBrain : NSObject {
    
    double operand;
    
    NSString *waitingOperation;
    
    double waitingOperand;
    }
    - (void)setOperand:(double)aDouble;
    - (double)performOperation:(NSString *)operation;
    
    

    43. Volvemos a CalculatorBrain.m. Esta es una implementación que soporta 2 operandos performOperation:

    - (double)performOperation:(NSString *)operation
    {
        if ([operation isEqual:@"sqrt"])
        {
            operand = sqrt(operand);
        }
        else
       {
            [self performWaitingOperation];
            waitingOperation = operation;
            waitingOperand = operand;
       }
       return operand;
    }
    

    Basicamente si se le pide a CalculatorBrain que realice una operación que no es simple (código invocado por el else) entonces CalculatorBrain llama al método performWaitingOperation (que todavía no hemos escrito) para realizar waitingOperation

    - (double)performOperation:(NSString *)operation
    {
        if ([operation isEqual:@"sqrt"])
        {
            operand = sqrt(operand);
        }
        else if ([@"+/-" isEqual:operation])
        {
            operand = - operand;
        }
        else
        {
      [self performWaitingOperation];
      waitingOperation = operation;
      waitingOperand = operand;
        }
     return operand;
    }
    

    Todavía necesitamos implementar performWaitingOperation.
    Cuando el mensaje es enviado a self significa que el mensaje se envia a si mismo. Otros lenguajes lo llaman this. performWaitingOperation es un método privado por lo tanto no lo pondremos en CalculatorBrain.h si no que ira en CalculatorBrain.m.

    44. Esta es la implementación de performWaitingOperation. Es importante que este código lo pongas en tu archivo CalculatorBrain.m  en alguna parte antes de la implementación de performOperation:.
    Esto es porque performWaitingOperation es un método privado. No se encuentra en la API pública. Debe ser declarado o definido antes de ser usado en un archivo. El mejor lugar es quizás entre la  implementación de  setOperand: y la de performOperation:.

    - (void)performWaitingOperation
    {
        if ([@"+" isEqual:waitingOperation])
        {
    
            operand = waitingOperand + operand;
        }
        else if ([@"*" isEqual:waitingOperation])
        {
    
            operand = waitingOperand * operand;
        }
        else if ([@"-" isEqual:waitingOperation])
        {
    
            operand = waitingOperand - operand;
        }
        else if ([@"/" isEqual:waitingOperation])
       {
    
           if (operand) 
           {
               operand = waitingOperand / operand;
           }
    
       }
    }
    

    Usamos la sentencia if {} else para que waitingOperation pueda utilizarce con cualquier operación despues realizamos la operación usando el operando actual y el que estaba esperando  (waitingOperand).
    Nuestro modelo esta implementado. Lo único que nos falta es hacer el controlador.


    Parte 6: 
    Implementar el controlador

    Lo que nos falta codificar es lo que sucede cuando se presiona un dígito (digitPressed:) o una operación (operationPressed:). Este código ira en nuestro CalculatorViewController. Ya hemos declarado estos métodos en la cabecera archivo (.h) , pero ahora tendremos que hacer la implementación en el archivo .m.


    45. Abrimos CalculatorViewController.m y seleccionamos y borramos todo el código de ayuda (“helpful” code) que se encuentra entre  @implementation y @end.

    46. Ahora volvemos a to CalculatorViewController.h y copiamos las dos declaraciones
    de los métodos en CalculatorViewController.m entre  @implementation y @end. Borramos los punto y coma y lo reemplazamos por  { } (llaves vacías). Debería verse así:


    //CalculatorViewController.m
    //Calculator
    //Copyright Stanford CS193p. All rights reserved.
    
    #import "CalculatorViewController.h"
    @implementation CalculatorViewController
    - (IBAction)digitPressed:(UIButton *)sender
    {
    }
    - (IBAction)operationPressed:(UIButton *)sender
    {
    }
    @end
    
    


    Veamos ahora un truco de debugging. Hay dos técnicas principales  de debuging, una es utilizar el debugger  que viene con el programa y la otra es usar printf, Objective-C provee la funcción NSLog() para esto. Vamos a utilizar NSLog() en  operationPressed: y después vamos a ejecutar nuestra calculadora y ver en la consola el resultado.


    El primer argumento es un NSString ( no una constante  char *, por eso no olvidar @), y el resto de los argumentos son los valores para cualquier campo % en el primer argumento. Un nuevo tipo del campo % ha sido agregado, %@, que significa que el argumento correspondiente es un objeto.

    47. Veamos este ejemplo con el método operationPressed:

    - (IBAction)operationPressed:(UIButton *)sender
    {
        NSLog(@"The answer to %@, the universe and everything is %d.", @"life", 42);
    }
    


    Si hacemos click en un botón que realiza una operación nos aparecerá “The answer
    to life, the universe and everything is 42.” ¿pero donde lo vemos?

    48. Luego vamos al menú Run en Xcode y elegimos Console. Nos aparecerá una ventana. Ahí será donde veremos la salida. También se puede hacer click en Build and Run (o Build and Debug)  en esa ventana para ejecutar el programa desde ahí. Hacemos click en una operación y deberíamos ver algo así:




    49. Reemplacemos la implementación de NSLog(). Notar que el argumento para operationPressed: es el botón que nos esta enviando el mensaje. Le preguntaremos a quien nos envia el nombre del botón (titleLabel, los objetos UIButton usan objetos UILabel ) ,luego preguntamos el UILabel retornado para saber el texto.  El resultado sera un NSString  con un  + ó * ó / ó - ó = ó sqrt.

    - (IBAction)operationPressed:(UIButton *)sender
    {
        NSString *operation = [[sender titleLabel] text];
    }
    
    




    50. Lo que necesitamos ahora es setear la variable Brain, para eso vamos a crear un método que cree y que retorne nuestra variable. Vamos a ponerlo justo antes de  @implementation.


    - (CalculatorBrain *)brain
    {
        if (!brain) brain = [[CalculatorBrain alloc] init];
      return brain;
    }
    


    Basicamente queremos crear una variable brain, por eso hacemos la creación de la misma si esta no existe. Creamos la variable brain y la inicializamos.

    51. Ahora que tenemos en el método CalculatorViewController.m que retorna a CalculatorBrain (nuestro Modelo)  vamos a usarlo.

    - (IBAction)operationPressed:(UIButton *)sender
    {
        NSString *operation = [[sender titleLabel] text];
        double result = [[self brain] performOperation:operation];
    }
    
    



    52. Tenemos el resultado de nuestra operación, solo necesitamos mostrarla en el display, para esto enviamos el mensaje setText:  a nuestro display outlet (recuerden que esta ligado al UILabel en nuestra Vista View). El argumento que vamos a pasar es un NSString creado utilizando stringWithFormat:. Es como printf() o NSLog() pero para objetos NSString. Notar que estamos enviando un mensaje directamente a una clasee NSString  (ni es una instancia de la clase sino la clase en si misma). Así es como creamos los objetos.

    - (IBAction)operationPressed:(UIButton *)sender
    {
        NSString *operation = [[sender titleLabel] text];
        double result = [[self brain] performOperation:operation];
    }
    




    53. Volvemos a CalculatorViewController.h y agregamos la variable de instancia userIsInTheMiddleOfTypingANumber. Este tipo de dato es BOOL y es una versión del tipo de dato booleano en Objective-C’s . Puede tomar dos valores, YES or NO.


    @interface CalculatorViewController : UIViewController {
    
        IBOutlet UILabel *display;
        CalculatorBrain *brain;
        BOOL userIsInTheMiddleOfTypingANumber;
    }
    
    

    54. Ahora volvamos a  CalculatorViewController.m y agregamos código a operationPressed: que lo que va a hacer es chequear si estamos tipeando un número y si es así actualizará el operando de CalculatorBrain para que sea el que ingreso el usuario (luego no sucedera más esto de estar en el medio del tipeo de un número).

    - (IBAction)operationPressed:(UIButton *)sender
    {
    
        if (userIsInTheMiddleOfTypingANumber) {
    
            [[self brain] setOperand:[[display text] doubleValue]];
    
            userIsInTheMiddleOfTypingANumber = NO;
    
        }
    
        NSString *operation = [[sender titleLabel] text];
    
        double result = [[self brain] performOperation:operation];
    
        [display setText:[NSString stringWithFormat:@"%g", result]];
    }
    
    

    Pero cuando se setea userIsInTheMiddleOfTypingANumber ? Bueno, se setea cuando el usuario empieza a ingresar dígitos. De todas maneras necesitamos implementar DigitPressed.
    Pensemos en la lógica de este método. Hay dos situaciones diferentes cuando hacemos click sobre un dígito. El usuario puede estar ingresando un dígito, en este caso agregamos el dígito a lo que ya venia digitando o no y en este caso es cuando queremos mostrar en el display ese digito y hacer notar que estamos ingresando un número.


    55. Agreguemos nuestra primera linea de código para digitPressed:. Nos va a retornar el dígito que fue presionado desde el titleLabel del UIButton que envio digitPressed:mensaje (el que envio).

    - (IBAction)digitPressed:(UIButton *)sender
    {
        NSString *digit = [[sender titleLabel] text];
    }
    
    

    56. Ahora que tenemos el dígito, lo agregamos a lo que ha sido tipeado (usando otro método NSString llamado stringByAppendingString:) o lo seteamos para que sea el nuevo número que estamos tipeando y hacer notar que comenzamos a tipear.

    - (IBAction)digitPressed:(UIButton *)sender
    {
    
        NSString *digit = [[sender titleLabel] text];
    
        if (userIsInTheMiddleOfTypingANumber)
        {
            [display setText:[[display text] stringByAppendingString:digit]];
        }
        else
        {
            [display setText:digit];
            userIsInTheMiddleOfTypingANumber = YES;
        }
    }
    
    

    Quizas se pregunten si  userIsInTheMiddleOfTypingANumber comienza con un NO.
    Sí, lo hace porque los objetos que heredan de NSObject  obtienen todas las instancias seteadas a cero. El cero para un valor del tipo BOOL es igual a NO. Hay que tener cuidado si el mensaje retorna un C struct (en este caso el resultado es indefinido).
    Eso es todo, ahora deberíamos ejecutar nuestra aplicación y ver si no tenemos ningún error de sintaxis.