Crear una aplicación productiva y social para iOs con interfaz web

En este artículo cuento mi experiencia de creación de la aplicación de iOs TimeBox ,desde el estudio de mercado, diseño, y elaboración de la interfaz gráfica  hasta la programación, creación de la base de datos y la aplicación web para redes sociales.

Lo primero que debemos hacer es tener claro qué tipo de aplicación queremos crear, para eso hay que tener una buena idea, y si la tenéis pero la aplicación ya está hecha, entonces comprar/bajaros todas las aplicaciones que han implementado vuestra idea y estudiarlas para encontrar en qué fallan, cómo las podéis mejorar y combinar, es decir, encontrad una motivación para programar, porque esto es duro amigos jeje (más…)

Aplicación para iPhone: Carta Astral

Esta aplicación surgió como un proyecto personal a partir de la necesidad de crear cartas astrales.

El reto comenzó con la forma en que se han realizar los cálculos de una carta astral. Por suerte, hay un software escrito en C, de los alemanes Dieter Koch and Alois Treindl y Astrodienst Zurich llamado SWISSEPH, conecté por consola ssh con mi servidor, utilicé wget con la última versión ( 1.76 ) y tras el make probé Swetest que a partir de una serie de argumentos de entrada muestra por pantalla un “ephemeris”, el resultado de leer una base de datos de posiciones de planetas, asteroides, estrellas, etc. y devolver las posiciones para cada una de las casas de dicha fecha y hora en el lugar especificado por longitud y latitud.Este programa además lanza una serie de datos para generar gráficas como “spreadsheets”.

Usando estos datos y un pequeño script:

//Analizar sintácticamente la fecha,hora,longitud y latitud
exec ("swetest -edir../src/ -b$utdatenow -ut$utnow -p0123456789
-eswe -house$longitud,$latitud, -fPlj -g, -head", $salida);
 foreach ($salida as $key => $linea)
      {
        $row = explode(',',$linea);
        $pl_name[$key] = $row[0];
        $longitude[$key] = $row[1];
        $house_pos[$key] = $row[3];
      };

Donde cada línea de salida de swetest se divide en arrays $row, con los elementos:0 = planeta,1 = longitud,2 = posición de casa, planetas en 0 – 9, cúspides en 10 – 21. Los nombres de los componentes,empiezan por 0 = Sol, que es la estrella del sistema solar,junto con la Luna=1 y Mercurio = 2 hasta Plutón = 9, los símbolos serían del 1 = Aries hasta 12 = Piscis. Para asociar la longitud y latitud al signo se hace una regla de 30 que limita la longitud y la combina con la casa asociada que devuelve el programa, de esta forma, podemos generar una imagen con instrucciones de dibujado geométrico (círculos, líneas, símbolos como texto)…Podéis ver un ejemplo aquí.

Para generar textos más completos he utilizado webs que los devuelven gratuitamente a partir de los datos, como grupovenus.
Finalmente, con un script en Python dentro de Google App Engine se crean todas las consultas en segundo plano, se construye el XML con HTML+CSS embebido, imágenes, etc. y se guardan, previo análisis sintáctico mediante la clase NSXMLParser en la base de datos SQlite asociada al Modelo de Datos, tal como vimos en los cursos.

Las animaciones de la app: las transiciones están hechas en OpenGL (HMGLTransition) y con las clases CAAnimation. El menú giratorio es una extensión de la clase UIGestureRecognizer ( KTOneFingerRotationGestureRecognizer ) modificada adaptando cada sección a un ángulo con una animación UIView…

Para la base de datos, primero se genera el modelo de datos, luego se compila y ejecuta la aplicación para que el mismo SDK cree el fichero SQlite así, tenemos la base sobre la que insertar los datos, una vez hemos rellenado todas las tablas, copiamos el fichero sqlite al proyecto y con un código como este:

 

NSString *storePath = [[self applicationDocumentsDirectory]
stringByAppendingPathComponent:
 @"Carta_Astral.sqlite"];
 
// Set up the store.
// For the sake of illustration, provide a pre-populated default store.
 
NSFileManager *fileManager = [NSFileManager defaultManager];
// If the expected store doesn’t exist, copy the default store.
if (![fileManager fileExistsAtPath:storePath]) {
NSString *defaultStorePath = [[NSBundle mainBundle]
pathForResource:@"Carta_Astral" ofType:@"sqlite"];
if (defaultStorePath) {
[fileManager copyItemAtPath:defaultStorePath
toPath:storePath error:NULL];
}
}

la primera vez que se ejecuta la aplicación, se copia al directorio de Documentos y ahí es donde se graban los datos del usuario, hago especial énfasis en que los tipos de datos complejos como Arrays de imágenes, textos y nombres se guardan en un tipo de dato del Modelo de Datos de Cocoa llamado Transformable.

En la sección de manuales hay tres tipos de manuales, los vídeos de youtube que se cargan sobre un objeto WebView, los textos con imagen que se cargan sobre una vista a mano y los pdf’s que se descargan a una caché con ASIHttp DownloadCache…Para paginar todos los documentos, libros, vídeos he usado una clase que crea un número infinito (gracias Andreas Katzian) de ScrollViews y los guarda en una caché de vistas para no tener que ir generándolos cada vez que se cambia de página, de forma que sólo se consulta una vez a la base de datos, se guarda una caché de tuplas y luego una caché de vistas con sus correspondientes botones, etc., así es mucho más eficiente y sencillo.
Para compartir textos e imágenes se utilizó ShareKit.

Veréis que las barras de navegación y las barras botones (tab) tienen una textura de fondo, esto se hace sobrecargando la clase correspondiente, en concreto el método de dibujado, para hacer que pinte una imagen por debajo y luego el resto del contenido.

La aplicación está siendo desarrollada

Persistencia de datos en iOS y Google Web Toolkit

En esta tercera entrega se explica brevemente como crear un modelo de datos usando XCode (internamente la gente de la manzana se ha apropiado de SQLite para hacerlo, han aprendido de la comunidad otra vez,al igual que en la época NeXT jeje) y cómo crear un servidor en la Google App Engine con las herramientas de Google Web Toolkit que nos proporcionará mediante servicios web la comunicación con una base de datos externa que desplegará datos a todos los dispositivos…lo que quiere decir es que puedes replicar un cambio desde cualquier lugar hacia todos a la vez de forma atómica…apasionante no?
Como no todo en esta vida es un camino de rosas, tratar con punteros y tipos de datos complejos tiene sus cosas, en XCode, o mejor dicho, con la versión de Objective C privativa que usamos para compilar nuestras aplicaciones para los dispositivos, tratar con la memoria es como jugar con granadas, desde que lanzas la granada al aire (reservas memoria) hasta que la recoges (liberas memoria), la parábola que surca en el aire podría ser un tanto extraña, también puede perderse la granada o ser reemplazada, meterse entre nubes o chocar con un avión que pasa jaja…con lo cual, nuestra tarea consiste en tratarla como a una mujer,delicadamente…para que no nos explote en la cara…Jugando con la memoria
Conceptos básicos para almacenar datos en iOS:

Core Data Foundation

  • Es un framework que garantiza la persistencia de los datos de nuestra aplicación
  • Genera una abstracción con clases que facilitan la implementación final del almacenamiento
  • Conecta la parte lógica con el modelo físico (ficheros, etc.)

Core Data Stack

  • El objeto que utilizaremos para gestionar colecciones de objetos es el Managed Object Context, también conocido en la jerga NeXT como NSManagedObjectContext, y representa el espacio de un único objeto, es decir, vamos  a utilizar este objeto para recuperar todo lo que tiene que ver con una entidad (una entidad es una tabla de la base de datos, también puede contener atributos en relaciones cuyos tipos sean otras entidades o tablas)
  • Con Managed Object Model (esquema de base de datos) guardaremos una colección de descripciones de entidades (nombres de tablas) pertenece a la clase NSManagedObjectContext  que relaciona Managed Object Context con el siguiente objeto que hace de puente:
  • Persistent Store Coordinator: guarda una colección de almacenes (Persistent Object Stores)

    en un conjunto de ficheros de datos persistentes (los ficheros de la base de datos, como en MySQL), su clase es NSPersistentStoreCoordinator y asocia los objetos de la aplicación con los registros de la BD.

Guía de uso del Modelo de Datos de iOS:

  1. Para hacer una consulta primero necesitamos crear las tablas, para ello lo que hacemos es crear un proyecto de tipo datos o bien añadimos un fichero al actual proyecto que sea XCode -> Core Data -> Data Model, en la pantalla de creación de entidades añadimos una tabla o entidad y la llamamos “Event”, por ejemplo. Dentro añadimos sus atributos con distintos tipos. Veréis que podéis usar un tipo “Transformable” que sirve para guardar Arrays, siempre y cuando los tipos que guarde el array sean soportamos por el protocolo del modelo de datos, además podemos crear relaciones como atributos que se conectan con otras entidades, aunque esto es más complejo, por ahora sólo veremos los casos simples. (ver más información >>)
    Podéis ver que el editor trae un soporte para la vista de un bonito Diagrama de Entidad/Relación de nuestra BD.
    Una vez creado el modelo vamos a New file -> Core Data -> NSManagedObject subclass (si lo que queremos es una clase que se encargue de realizar todas las operaciones setter & getter por nosotros y así asociemos el contenido al modelo de datos fácilmente con funciones, aunque también podemos hacerlo aún más automático con un framework como este: Fremont)
  2. Para crear objetos es necesario invocar a NSEntityDescriptor con el tipo de objeto y el contexto, veamos un ejemplo:(os recomiendo siempre escribir el código el inglés ;) . Supongamos que nuestra tabla (modelo de datos) event tiene los campos, longitud y latitud de tipo Float, un título (NSString -> String en Modelo de Datos), y una fecha de tipo date:
    //Crear y configurar una instancia de una entidad Event
    - (void) createEvent {
    Event *event = (Event *) [NSEntityDescription insertNewObjectForEntityForName:
    @"Event" inManagedObjectContext: managedObjectContext];
    //Crear un tipo de dato coordenada para mapas de google, así nos acostumbramos a usarlos:
    CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(37.123123, -3.321321);
    //Ahora lo tenemos muy fácil ,sólo tenemos que usar los setters y getters generados por XCode:
    [event setLatitude:[NSNumberWithFloat:coordinate.latitude]];
    [event setLongitude:[NSNumberWithFloat:coordinate.longitude]];
    [event setCreationDate:[NSDate date]]; //"date" es la fecha de hoy
    [event setTitle:@"Mi primer evento"];
    }

    y eso sería todo el código, ahora pasamos a grabar los datos para hacerlos persistentes

  3. Para guardar un objeto se utiliza el método savede NSManagedObjectContext, si ocurre algún error se guardará la información del mismo (el motivo por ejemplo) en la variable NSError dispuesta a tal efecto.
    NSError *error;
    if ([managedObjectContext save:&error]){ 
    //prestar especial atención al ampersand...
    //referencia de memoria!
    //Gestión del error aquí
    }

    Podemos intentar capturar errores con una captura de excepciones (@try { } @catch (NSException *exception) { } @finally { } ) pero esto no es recomendable, además de que debemos recordar la cadena de respondedores, aquí se aplica el mismo cuento y tendríamos que ir hacia arriba en la lógica de la programación para capturar la verdadera excepción de la pila de llamadas…cosa que es bastante tediosa, por eso es mejor pensar las cosas bien y hacerlas mejor jeje, con la práctica todo se consigue ;)
    Fetch Request -> execute!

    Si queremos recuperar el objeto no tenemos más que usar, como se hace en php y mysql una petición tipo “fetch”, es decir, con NSFetchRequest especificamos la entidad, aquí tenéis un ejemplo completo.
    En resumidas cuentas, hay que crear un objeto NSFetchRequest, reutilizamos el objeto de la descripción de una entidad y asociamos esta al primero con setEntity:

    NSFetchRequest *request = [[NSFetchRequest alloc] init]; 
    //cuidado que esto no se libera. lo hace sólo...:O
    NSEntityDescription *entity = [NSEntityDescription 
    entityForName:@"Event" inManagedObjectContext: managedObjectContext];
    [request setEntity:entity];
    //Para realizar un ORDER BY fecha típico usamos un descriptor de ordenación:
    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:
      @"creationDate" ascending:NO];
    NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
    [request setSortDescriptors:sortDescriptors];
    //ahora sí, liberamos los descriptores
    [sortDescriptor  release];
    [sortDescriptors release];
    //Con todo configurado, vamos a ejecutar la consulta y
    // guardar el resultado en una matriz modificable:
    NSError *error1;
    //Atención al parámetro "mutableCopy"!!
    NSMutableArray *results = [[managedObjectContext 
       executeFetchRequest:request &error1] mutableCopy];
    if ( results == nil ){
        //Manejar el error!
    }
    //Para borrar necesitamos un NSError igual que cuando guardamos con save
    NSError *error2;
    [managedObjectContext deleteObject:objetoEventoParaBorrar];
    if (![managedObjectContext save:&error2){
     //error...
    }

    Nunca os asustéis de un error como éste: significa que no habéis asociado bien el tipo de dato al diccionario usado para realizar una inserción en masa, ánimo!

Que los robots escriban código por nosotros: Servicios Web

Recordamos del curso de servicios web que escribir XML y código de un servicio web no es tarea de humanos, para eso existen frameworks que harán el trabajo duro por nosostros, Fremont es la parte cliente, en Objective C, para la parte del servidor tenemos los transformadores que ya vimos, BPEL, etc.

Gracias al uso de un servidor asociado a Google App Engine, crearemos un conjunto de servicios usando Google Web Toolkit 2.3.0 , que una vez probados en red local podremos desplegar en el servidor Java de GAE asociado a nuestra cuenta de usuario.

Pasos para la creación de un servidor de datos por medio de servicios web con Eclipse:

  1. Descargar el IDE Eclipse (Hellios SR2 por ejemplo) y el plugin para GWT.
  2. Crear un proyecto de prueba y ejecutarlo
    Ir a File -> New – > Project … -> Google -> Web Application Project . Le dáis un nombre al proyecto y al package sencillos, os recomiendo usar un área de trabajo nueva para no mezclar, aseguraos de que la opción de Google Web Toolkit está marcada y que estamos usando el SDK de GWT – 2.3.0 asó como el Google App Engine con su SDK (el que sea, en mi caso el 1.5.0) y marcad que genere el código de ejemplo para que todo fluya :) . Si todo ha ido bien debéis poder desplegar la aplicación en vuestra cuenta de google sobre el App Engine y ver el producto generado…ya podéis ser un 3% más felices hoy jeje
  3. Crear un paquete de servicios para nuestro servidor de aplicaciones de iPhone.
  • Pasos:
    • Crear paquete
      En el directorio src pinchamos con el segundo botón y hacemos New -> Package ,de nombre le damos servicios_iOS, por ejemplo. El primer servicio que vamos a crear es un servicio simple, que muestre una lista de eventos, pero antes debemos crear el modelo de datos que ha de persistir (guardarse) en el Data Store de Google Web Toolkit.
    • Crear modelo de datos
      Dentro de nuestro paquete hacemos click con el botón secundario del ratón y -> New -> Class, de nombre “Evento”, claro ,jeje…me gusta escribir todo el código de un servidor en inglés ya que es algo que veremos poco y puede que solamente nosotros…como opciones pues, es una clase pública, etc. Dentro de la clase añadimos ,como os imaginaréis,
      private Key id; private String titulo; private float latitud; private float longitud;
      y luego usaremos los generadores de setters y getters que tanto nos gustan de Java en Eclipse, así como los constructores.

      Hay un caso curioso, como veréis he puesto Key, en lugar de un int o un Long para la llave principal, esto es debido a que si queremos usar el objeto como algo persistente y almacenable dentro de otra clase, esta llave le permite indexar al objeto por lo que puede serializarse, convertirse en una cadena y quedar guardado en las bases de datos de Google.
      A cada campo le debéis añadir los protocolos antes de su declaración “@Persistent“, y a la llave principal le añadimos @Primary Key , y también: @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY).
      A la clase Evento además le añadimos el siguiente prefijo: @PersistenceCapable(indetityType = IdentityType.APPLICATION)
    • Añadir complejidad al modelo de datos
      Para aquellos que no se contentan con un modelo de datos simple y quieren asociar una lista de cosas (vaya usté a saber qué más cosas…) pueden usar el prefijo: @Persistent(embedded=”true”) para variables del tipo private List<TipoDeDatosPropio> miListaDeCosas; Sin olvidarse,claro, de que el tipo de datos propio ha de llevar nuestro querido prefijo @PersistenceCapable(indetityType = IdentityType.APPLICATION) y que ha de contener una llave o Key id para poder serializarse y empaquetarse dentro de un atributo persistente de otra clase persistente
    • Crear servicios que usen el modelo de datos
      Tenemos un modelo donde almacenar datos, utilizaremos un fichero HTML en el directorio /war/ sencillo donde colocar un formulario con los campos título, longitud,latitud, fecha y cuya acción (o action, con method=”POST”) irá a “/nombreProyecto/nombreAccionServicio”.
      Para crear el servicio que atenderá la petición vamos a nuestro paquete y -> new -> class -> “nombreAccionServicio”.  A la clase, le ponemos el prefijo @ServiceName(value=”nombreAccionServicio”) y hereda (con la palabra clave extends) de la clase HttpServlet, de modo que debemos sobrecargar la función
      public doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, DatastoreFailureException { //… },
      para que atienda la petición enviada desde el fichero HTML por el método POST. Igual que en PHP.
      Importante que la respuesta se haga de una manera parecida a esta:

      resp.setContentType("text/html");
      PrintWriter out = resp.getWriter();
      out.println("< !DOCTYPE HTML PUBLIC \"-W3C//DTD....."); //etc

      Una vez que hayamos creado nuestras inicializaciones de datos de nuestro modelo (instanciado las clases oportunas), debemos guardarlos usando un gestor de Persistencia, lo que viene a ser una clase de JDOHelper.getPersistenceManagerFactory(“transactions-optional”); que por medio de la función makePersistent(clase_instanciada) se almacenará (después debemos hacer un close(), claro jeje)

    • Añadir las rutas al war/WEB-INF/web.xml –> este es el enrutador de acciones, asocia un nombre de ruta en una url (con un patrón) a un servicio web (con una clase que extiende de HttpServlet). Para ello,abrimos el fichero y creamos dentro del web-app un hijo servlet que especifica la asociación con el programa usando la palabra clave servlet-name:nombreServicio y servlet-class: nuestra clase nombreAccionServicio.
      Por último, para el mapeado, es decir, la forma en que un patrón o expresión regular asocia una ruta url a nuestro servlet, añadimos un hijo de “web-app” llamado servlet-mapping con el mismo servlet-name:nombreServicio y url-pattern:/nombreAccionServicio
  • Repetir operación para crear servicios que generen listas de datos, en nuestro caso nos hará falta que se genere un listado XML de todos los <eventos> con sus etiquetas para cada campo que tiene el modelo de datos. Para ello, añadimos una función a la clase Evento que genere la cadena XML como un string en UTF8 y un Servlet que la muestre por pantalla, en este caso la cabecera ha de ser text/xml ;)
  • Más información en este tutorial >>

Análisis sintáctico: XML – Servicio Web – iOS

Como ya hemos visto antes el funcionamiento de la persistencia de datos en iOS con un Modelo de Datos, pasaré directamente al análisis de un XML, aunque esto debería hacerse automáticamente con un framework como Fremont, lo importante es que os quedéis con que las clases que permiten estas tareas son NSURL, NS/Mutable\URLRequest,y NSURLConnection. Estas descargan los datos y podemos realizar el análisis sintáctico con varios tipos de “parser”, SAX: envío de notificaciones a medida que el analizador sintáctico va leyendo la cadena XML recibida por NSURL, o bien DOM:  es un analizador que lee toda la cadena XML y construye su representación completa. Para utilizar estos analizadores debemos incluir al proyecto el fichero libxml2 de las librerías del SDK (tanto SAX como DOM), o bien NSXMLParser (sólo SAX). Aunque existen alternativas como TBXML, TouchXML, KissXML, TinyXML, GDataXML, etc. –> ver comparativa >>

Pasos para utilizar NSXMLParser:

  1. Crear el parser con la información recibida de NSURL.
    NSXMLParser *parser  = [[NSXMLParser alloc] 
    initWithData:xmlData];
     //recibidos por NSURL
  2. Asignar un delegado (delegate), una clase que se encarga de recibir los eventos,i.e., que hereda de <NSXMLParserDelegate>
    [parser setDelegate:self];
  3. Comenzar a parsear con [parser parse];

A partir de aquí, se trata de usar los eventos del delegado (eventos de NSXMLParserDelegate) que son: didStartElement -> empieza un elemento, didEndelement, didStartDocument, etc. de forma que al principio del documento inicializamos los datos (un array modificable, por ejemplo), y cada vez que encuentra un elemento de tipo titulo pues añade un elemento al array y luego al terminar un elemento de tipo evento, pues añade el array de propiedades de un evento al array de eventos…fácil…Importante implementar también la función:

- (void)parser:(NSXMLParser *) parser parseErrorOccurred:
  (NSError *)parseError;
ya que nunca se sabe qué errores se pueden cometer, por ejemplo que el documento no llegue completo, y la aplicación pende de un hilo por una mala gestión de este tipo de errores…algo que no da buena imagen por lo que hay que pulir estos detalles…

Uso de caché

La utilización de caché tanto para datos persistentes como para datos dinámicos es importante a la hora de realizar aplicaciones para móviles ya que está muy penalizado el uso de conexiones a Internet, es más rentable llegar a un equilibrio de carga de datos entre el servicio web y los usuarios de los dispositivos, por eso os presento las dos tareas a realizar en un proyecto serio…

Caché de imágenes con ASI Http Framework

En esta dirección encontraréis un framework sorprendente para realizar tareas con análisis sintácticos de XML, descarga de datos a través de urls, caché, etc.

Es una maravilla ver como funcionan los ejemplos, hay uno,especial de caché en el que se dispone una serie de descargas de imágenes y a cada una de ellas se le asocia un puesto en una clase cola-de-espera que tiene asociada una barra de progreso por lo que va actualizando el estado conforme se van descargando los datos…impresionante :)

Caché de datos con SQlite

Lo que podéis hacer es utilizar una caché para guardar la información en un modelo de datos, durante unos días en el iDevice, pasados esos días, vuelve a sincronizarse con el servicio de Google App Engine y reescribimos toda la estructura de datos del programa con nueva información, así nos ahorramos realizar peticiones al servidor continuamente, lo que ralentizaría mucho la carga y si la aplicación es muy usada puede generar un cuello de botella que ha de evitarse.

Para hacer esto nos viene bien los datos de configuración de un usuario, lo veremos en la próxima entrega del curso de aplicaciones para iOS, aquí mismo.

Recordar que podéis utilizar recursos como http://wiki.gnustep.orghttp://stackoverflow.com/ para solucionar vuestras dudas tanto con Objective C como con Java Google App Engine y GWT.

En cualquier caso, los ejercicios de esta entrega están claros cuáles son: desplegar una aplicación en GAE con GWT y pasar los datos a un modelo de datos de XCode (SQlite)…hay cientos de tutoriales en internet sin embargo si tenéis cualquier duda, mandadme el fichero y lo intentaré corregir.

<< Volver al curso de programación de aplicaciones de iOS | Siguiente lección: Configuración y traducción de una aplicación de iOS >>

Introducción al IDE de XCode, Objective C e iOS

Curso de creación de aplicaciones de iOS

Hardware y Software que “nos exigen”

  • OSX (Snow Leopard al menos) actualizado a la última versión, para poder instalar el IDE de la manzanita. Nos quieren obligar a comprarnos un Mac, peeero, también podéis instalaros el VMWare Fusion  en un PC (o el VMWare normal pero sin soporte OpenGL), u Oracle VM VirtualBox sobre Windows o GNU/Linux y descargar una imagen de OSX de la red para este software-emulador, e incluso…crearos un Hackintosh, que no es muy caro y están de moda jeje
  • XCode 4 + iPhone SDK: podéis descargarlo de Internet si no tenéis dinero para la licencia de developer, aunque si queréis publicar pronto una aplicación podéis daros de alta en el programa de desarrolladores de la empresa, pagando claro!…
  • iPhone/iPad/iPod: pues hombre, estaría bien  tenerlo para poder probar lo que escribamos, ya que el simulador (no es un emulador!) que trae el IDE se comporta de manera algo distinta a como debería ser…pero bueno, para empezar tampoco vamos a tirar la casa por la ventana jeje Si tienes un dispositivo intenta instalarte el AppWizard para el siguiente paso -no te diré como conseguirlo-

Hardware de los dispositivos iOS

  • El iPhone 4 tiene una resolución de pantalla de 960×640 a 326 ppi.
  • El iPhone 3 tiene una resolución de 480×320 píxels
  • La resolución del iPad es de 1024×768 a 132 ppi

Habilidades que necesitamos para programar aplicaciones de iOS

  • Programar en Objective C: programar aplicaciones de iPhone en XCode es, básicamente, empezar a conocer cómo funciona Objective C y cómo asociar los objetos del Interface Builder a nuestro código. Para eso existen cientos de tutoriales, algunos más famosos que otros como “Masters of the Void“, pero lo mejor es seguir un libro gratuito de la propia empresa (en el apartado de desarrolladores) o ir a una biblioteca y pillarse libros como Objective C for dummies, iPhone application development for dummies, y luego algo más serio como iOs4 Programming Cookbook de O’Reilly, ya que contienen multitud de ejemplos y trucos para desarrollar todo lo que deseemos/necesitemos.
  • Hay un libro de la web de MacProgramadores bastante bueno sobre Objective C con especial énfasis en su origen (NeXT) y XCode, lo han borrado de la web, pero seguro que lo encontráis…

Introducción a XCode e iOS4 SDK

Conceptos básicos:

XCode

  • Xcode es el IDE que nos ofrece Apple para desarrollar aplicaciones de iOS.
  • El SDK (Software Development Kit) incorpora herramientas para el desarrollo (entre ellas, el simulador de iPhone/iPad).
  • El lenguaje de programación es Objective-C
  • Simulador de iOS: Permite simular tanto iPhone como iPad en nuestro Mac.
  • Interface Builder: Editor visual para diseñar interfaces de usuario para nuestras aplicaciones.
  • Instruments: Herramienta de análisis que nos ayuda a optimizar y monitorizar la aplicación

Sistema Operativo iOS

  • El Sistema Operativo de los dispositivos de Apple (iOS), está formado por un conjunto de capas, que conforman el conjunto de servicios ofrecidos por el dispositivo. Arquitectura:
  • Cada capa de la arquitectura está compuesta por un conjunto de frameworks
  • La capa Core OS es la base del sistema operativo. Se encarga de realizar la gestión de memoria, el sistema de ficheros, conexión a la red y procesos que interactúan con el hardware
  • Core Services nos permite el acceso a los servicios básicos, tales como la agenda, el acceso a la base de datos, preferencias, conexión a servidores y procesamiento de URLs, etc…
  • La capa Media nos permite la ejecución de tareas multimedia. Entre ella el acceso al Audio, OpenGL, Imágenes y PDF, Animaciones, etc…
  • Cocoa Touch nos permite acceder al acelerómetro, los eventos y controles táctiles, la jerarquía de vistas, alertas, etc…gestiona la interacción visual con el usuario
  • Novedades de iOS4:
  • Multitarea: mientras ejecutamos una aplicación, al pulsar el botón “home” no se cierra sino que pasa a segundo plano, pueden continuar ejecutándose o pueden suspenderse
  • Notificaciones Locales: completa el sistema de notificaciones push (desde un servidor remoto que nosotros mismos podemos crear)
  • Core Motion: conjunto de interfaces para acceder a toda la información basada en el movimiento (giroscopio ,motion-based)
  • Data protection: sistema integrado de encriptación
  • Soporte para pantalla de alta resolución (adaptación a la antigua)

Frameworks

  • Un framework es un conjunto de librerías que nos permite añadir una funcionalidad concreta a nuestra aplicación
  • Por defecto, cuando creamos un proyecto, tenemos los frameworks esenciales para el funcionamiento básico
  • Por ejemplo, para usar bases de datos SQLite usaremos su framework, simplemente con copiar el directorio descargado de su web, aunque en la versión de XCode4 ya viene integrado, también haremos lo mismo con Cocos2D
  • Cocoa Touch: conjunto de frameworks orientados a objetos que permiten el desarrollo de aplicaciones nativas para iOS

Crear una aplicación

Pasos

  1. Abrir XCode y crear un proyecto
  2. Diseñar la interfaz de usuario
  3. Escribir el código asociado de la aplicación
  4. Compilar, ejecutar y probar (vuelta al paso 2)
  5. Medir y ajustar el rendimiento de la aplicación
  6. Producir y publicar la aplicación

Tipos de proyecto

  • Cocos 2d: para crear juegos (necesita el framework Cocos2D),lo veremos en otro curso, de programación de videojuegos para iPhone
  • Navigation-based : presentan la información de forma jerárquica usando múltiples vistas (diseño de pantallas)
  • Open GL : igual que cocos, además de juegos podemos crear cualquier tipo de aplicación que represente imágenes, animaciones o gráficos 3D
  • Split View-based : Aplicaciones enfocadas para iPad que muestran más de una vista en la pantalla al mismo tiempo (un control listado y una vista detalle de elementos normalmente)
  • Tab Bar : presentan una interfaz de “radio” que permite al usuario elegir entre varias opciones
  • Utility : aplicaciones que presentan una vista principal y permiten que el usuario acceda a otra vista para realizar personalizaciones básicas
  • View-based : una vista simple para implementar la interfaz
  • Window-based : plantilla universal (cualquier dispositivo con iOS) que sirve de punto de partida con un delegado y una ventana. Útil para aplicaciones con jerarquía propia

Recordar que no todos los tipos de aplicaciones pueden ser universales (para cualquier iDevice) y las que lo son tienen diferentes diseños de interfaces de usuario en subdirectorios con su nombre.

Interfaz de XCode

XCode4: editor en una ventana

XCode4: Interface Builder

XCode4: Git integrado

Tipos de ficheros de un proyecto

  • Info.plist: como todos los plist, es un XML (introducido por NeXT) constituído de propiedades de configuración en UTF-8
  • Ficheros.h: declaraciones de interfaces ,clases y atributos
  • Ficheros.m: implementación de clases y métodos definididos en los .h (aunque no necesariamente requieren un .h)
  • Ficheros .pch: cabeceras precompiladas que se añaden a las clases
  • Ficheros de objetivo: resultado de la compilación de un conjunto de ficheros de código, puede ser una librería o una aplicación. Un conjunto de objetivos forman un producto. Lo utilizaremos para publicar una aplicación en la tienda

Perfiles de compilación

  • Depuración: incluye la opción de depuración al compilar con gcc, es más lenta pero encuentra errores lógicos. Se puede depurar en el editor de texto, mini-depurador, depurador y en la consola [(gdb)]. Opción: GCC_GENERATE_DEBUGGING_SYMBOLS. Podemos usar breakpoints.
    Mientras el programa está siendo depurado podemos modificarlo (como pasaba en VisualBasic y otros lenguajes! :D) : cambiar el nº y tipo de argumentos de una función o método que esté en la pila,el tipo del valor de retorno o el nombre de una  función o método también en la pila, así como cambiar el tipo de las variables estáticas, un fichero NIB, añadir clases o cambiar sus métodos…
    Leer más >
  • Lanzamiento: mayor rendimiento de la app, tamaño de objeto final compilado menor.
  • Distribución: como el anterior pero necesita un certificado o firma de Distribución válida (con apple hemos topado xD, aunque podemos arreglar esto con algún que otro truco de Cydia…). Se añade meta-información necesaria para ser validada en la tienda.

Introducción a Objective C

¿Qué es?

  • Pequeño set de extensiones de ANSI C
  • Sus añadidos a C están basados en Smalltalk, uno de los primeros lenguajes orientados a objetos
  • Diseñado para dotar a C de toda la potencia de la orientación a objetos

Clases

  • Sintáxis normal de creación de clases: interfaz (.h) + implentación (.m)
  • Ejemplo de clase Persona.h:
    @interface Persona : NSObject {
    NSString *nombre;
    NSString *apellidos;
    }
    -(NSString*) nombre;
    -(NSString*) apellidos;-(void) setNombre: (NSString *) _Nombre;
    -(void) setApellidos: (NSString *) _Apellidos;
     
    +(NSString *) soyMetodoEstatico: (NSString *)  mensaje;
    @end

    Ejemplo de clase Persona.m:

    #import "Persona.h"
    @implementation Persona
    -(NSString*) nombre {
    return self.nombre;
    }
    -(NSString*) apellidos {
    return self.apellidos;
    }
    -(void) setNombre: (NSString *) _Nombre {
    self.nombre = _Nombre;
    }
    -(void) setApellidos: (NSString *) _Apellidos{
    self.apellidos = _Apellidos;
    }
    +(NSString) soyMetodoEstatico: (NSString *) mensaje{
    return mensaje;
    }
    @end
  • Ejempo de función que toma datos de la clase padre de la que hereda:
    - (id) init {
    if ( self = [super init] ) {
    [self setNombre:@"nombre por defecto"];
    [self setApellidos:@"apellidos por defecto"];
    }
     
    return self;
    }

    self es la propia instancia del objeto y super es la clase de la que hereda

  • Declaración de un método:
  • A través del corchete accedemos a un objeto, que es el primer parámetro, el segundo es la llamada al método, es como hacer
    $this->método en PHP , o puntero_a_clase -> método , en C / C++,Java.
    Por ejemplo , un método sin entrada

    [object method];

    Con entrada:

    [object methodWithInput:input];

    Con salida:

    output = [object methodWithOutput];

    Con entrada y salida:

    output = [object methodWithInputAndOutput:input];
  • Recordar que si queremos especificar una función con varios parámetros se ponen dos nombres, uno para el nombre clave de llamada y otro para el nombre interno del parámetro, el primer nombre de nombre clave no se pone, ejemplo:
    -(void) miFuncion: (int)parametro1 nombre1: (bool) parametro2 nombre3:(NSString*) parametro3;
    a esta función accedemos así, ejemplo con datos:
    [miObjeto miFuncion:7 nombre1:true nombre3: @”cadena”
    Fijaros que una cadena siempre se precede de una arroba.
  • Propiedades de una clase: (@property)
    permiten generar automáticamente métodos de acceso para los atributos de una clase, podemos sobrecargar los métodos setter & getter si no usamos las especificaciones por defecto de una propiedad, para saber cuál nos conviene primero debemos ver la gestión de memoria. Por defecto, el setter se añade automática a la propiedad definida como setNombre_variable (set+UCase(nombre_variable)), y el getter es el nombre de la variable atributo tal cual, en Persona.h el setter es setNombre y el getter nombre. Fácil.
    Para que podamos usar las propiedades es obligatorio inicializarlas con @synthesize en Persona.m (la implementación de la clase),este genera los setters y getters automáticamente. Una vez hecho además podemos especificar en esta línea de sintetización el nombre local del acceso o accesser, algo como @synthesize nombre = _nombre_local;
  • Ver más >

Gestión de memoria!: de lo más importante en Objective C

  • Con el método dealloc liberamos memoria utilizada en las instancias de nuestras clases, lo vamos a sobrecargar para hacer “release” de todas las instancias de objetos que hayamos creado
  • Se debe liberar por completo el propio objeto también ,con [super dealloc] .
  • Debemos liberar las variables de tipado fuerte
  • Para aprovechar la memoria tenemos que crear nuestro propio “recolector de basura”, algunos ven esto como muy buena idea y otros no son partícipes de que el lenguaje no tenga las bondades de Java…
  • Para gestionar qué variables están siendo utilizadas en memoria, tenemos lo que se llama el “contador de referencia” de Objective C, sólo tenemos que especificar qué hacer con las variables al asignarlas, crearlas, copiarlas, pasarlas por parámetro, etc. y la ejecución del programa hace el resto.
  • Con alloc inicializamos memoria en función del tipo o clase de una variable, por ejemplo, cuando declaramos una variable de tipo NSNumber con una instrucción como esta:
    NSNumber *number = [[NSNumberalloc initWithFloat:8.75;
    lo que pasa internamente es que el contador de referencias se pone a 1.
  • Con retain aumentamos el contador de referencias en uno, por ejemplo si deseamos liberar en un contexto la variable pero no queremos que se pierda la variable porque se usa en otro contexto, por ejemplo:
    -(
    void) setNombre: (NSString *) _Nombre {
    [self.nombre
    autorelease;
    self.nombre = [_Nombre
    retain;
    }
    Usando autorelease se libera al objeto de manera diferida, es decir, es posible usar la variable de la primera línea durante un tiempo más, le dice al compilador que la variable self.nombre se decremente al salir del contexto de la clase de la función setNombre, después le asigna un nuevo valor, el del parámetro, pero a este le dice que lo retenga aunque se borre fuera, para que sea gestionado desde la clase.
  • Con release se decrementa el contador en una unidad, si tiene valor 1 ,se borra la variable, es decir, le decimos al sistema que no la necesitamos y éste llama a dealloc automáticamente (primer punto)
  • Ojo entonces, si por ejemplo creamos un método en una clase para asignar un valor y hacemos que use release para luego hacer retain con el nuevo valor que le pasamos, siendo este no una variable sino una llamada de una función, algo así:
    ________
    – (
    void) setNombre: (NSString*) _Nombre {
    [self.nombre
    release;
    self.nombre = [_Nombre
    retain;
    }


    persona
    * ego = [[personaalloc init];

    [ego
    setNombre:[ego nombre];
    ________
    al llamar a la función setNombre, primero se libera el objeto self.nombre antes de retenerlo, por lo que ya no hay espacio reservado en memoria para esta variable y aquella pasa a estado incoherente ya que el valor que le hemos pasado es precisamente el que hemos liberado, para solucionarlo ponemos autorelease en lugar de release.
  • Ahora podremos saber qué tipo de gestión de memoria asignarle a una propiedad, por ejemplo para el nombre en nuestra clase Persona.h:
    @property (retain) NSString* nombre;
    al poner retain hemos declarado que el setter (función como setNombre pero automática) debe hacer retain sobre el valor de entrada. Pero podemos especificar con setter=setNombre nuestra propia función ;)
  • A veces tenemos que tener cuidado con lo que liberamos de la memoria, no podemos dejar todo en autorelease porque al pasar la referencia de una variable entre funciones es posible que se libere antes de tiempo, sin embargo de esto nos daremos cuenta cuando el simulador haga CRASH! jaja

Variables

  • Tipado débil: int, bool, float, etc.
  • Tipado fuerte: objetos como NSString, NSNumber, etc.
  • ejemplos de métodos con tipos distintos:
    - (int) multiplica: (int)a por:(int)b {
    return a * b;
    }
     
    - (NSString *) cadenaResultado: (int)resultado {
    NSString *res = [NSString stringWithFormat:@"El resultado es: %d", resultado];
     
    return res;
    }
     
    int resultado = [self multiplica:2 por:3];
    NSString *cadena = [self cadenaResultado:resultado];
    NSLog(@"%@", cadena); //devuelve 6
  • Un protocolo (@protocol) permite que la lista de métodos que contienen se puedan implementar por cualquier clase, puede especificarse al crear una clase en su definición de la clase que hereda de otra…
    @protocol MyProtocol
    - (void)requiredMethod;
    @optional
    - (void)anOptionalMethod;
    - (void)anotherOptionalMethod;
    @required
    - (void)anotherRequiredMethod;
    @end
     
    @interface ClassName : ItsSuperclass < protocol list >
  • Otro método para depurar es usar el log, la función NSLog(cadena) usa el mismo formato que un printf, recordar el formato:

    NSLog(@”La fecha y hora actual es %@”, [NSDatedate );

XCode Organizer

  • Una vez creada la aplicación, si queremos usarla en un iDevice, tenemos que desplegarla (necesita certificado), conectar el dispositivo por USB y probar! Pero también podemos usar el simulador ,aunque no refleja el comportamiento exacto (real) de la aplicación si que sabremos si funciona o no :)

Consejos

Diseñar como un ingeniero y producir como un artista: echad mano de vuestro amigo el arquitecto de información para encontrar la mejor manera para guardar los datos,… no es lo mismo una base de datos en SQLite que un conjunto de ficheros XML.

  • Hay que conocer el flujo de la información en el sistema de aplicaciones, por ejemplo un sistema de etiquetas debe ser recurrente en cuanto a que hay tags que se repiten, la forma óptima de hacer esto es como lo hace WordPress o algunas aplicaciones web si lo prefieres. Debemos consultar a nuestro amigo experto en marketing para el nombre del programa, así será el dominio para publicarlo, también nos aconseja qué tipo de características son viables en cuanto a lo que el público demanda y nos da su perspectiva como experto lo bonito, barato y bueno. Efectivamente, necesitamos conocer cómo construir diagramas pero no son indispensables, aunque sería ideal diseñar previamente la app con UML
  • Beta tester: antes de empezar si quiera a pensar en querer programar algo para iOS ,lo primero que debemos hacer es conocer los dispositivos, el iOS y OSX, todos los widgets del sistema operativo, así que al menos tenemos que haber probado todas las aplicaciones antes mencionadas, aunque sea en el ifón de un amigo, no vale haberlo visto en vídeo, tenemos que tocar la esencia de la magia de esta tecnología para sentir qué queremos hacer realmente.

Otros tutoriales para conocer el IDE XCode 3: nos sirve para empezar.

Ahora que conocemos el IDE de la mano de Noemi gracias a sus tutoriales de Youtube, vamos a seguir un poco más adelante, tenemos la ayuda necesitamos aquí:

Ejercicios (Deberes :)

  1. Ejercicio 1: crear un proyecto tipo “View-based”, crear una clase Persona y otra clase Usuario, establecer propiedades en las clases asociadas a sus atributos, una clase Usuario debe tener un atributo que sea una clase Persona.
  2. Ejercicio 2: Crear un NSArray e inicializarlo con clases NSNumber, luego mostrarlo con NSLog
  3. Ejercicio 3: Crear un NSMutableArray e inicializarlo con clases Persona, añadir  a dicha clase la función (sobrecargada) description para que NSLog pueda mostrar el contenido de todos sus atributos

Para corregirlo podéis enviármelo con el formulario de contacto.

<< Volver al curso de programación de aplicaciones de iOS

Siguiente: conectar interfaz con el código

Aplicaciones para MAC con XCode y SQLite

He estado realizando en los últimos meses algunas tareas para clientes, tanto extranjeros (de Grecia) como locales y aquí van algunos consejos a raíz de las experiencias que he tenido tras la investigación del IDE XCode y del Snow Leopard.

Ahora os muestro un vídeo con un ejemplo sencillo de lo que se puede hacer con Objetive C de XCode de Apple y SQlite, creación de una base de datos, una tabla y dos tuplas para luego mostrarla en un objeto de Cocoa:

Lo primero, para diseñar interfaces y escribir código hay que descargarse las herramientas, esto es, la última versión de XCode y las Developers Tools, así podremos compilar el código que escribamos asociado a las interfaces.

Para poder tener un almacén de datos exportable y que se pueda utilizar en cualquier plataforma (iPhone,iPod, PC, MAC,etc) usaremos SQLite, y para comunicar este motor con XCode usaremos el framework FMDB (del cual hay una versión específica para iPhone/iPod) ,esto es tan fácil como descargarse el framework y copiar los ficheros, después se crea una clase base de datos como esta:

#import 
#import "FMDatabase.h"
#import "FMDatabaseAdditions.h"
 
#define FMDBQuickCheck(SomeBool) { if (!(SomeBool)) {
 NSLog(@"Failure on line %d", __LINE__); return 123;
 } }
 
@interface bd : NSObject {
	FMDatabase *fmdb;
 
}
- (void) crearBD;
@end

La implementación:

#import "bd.h"
 
@implementation bd
 
- (void) crearBD {
 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
 fmdb = [FMDatabase databaseWithPath:
   @"/Users/juaxix/Documents/miBD.sqlite"];
 if (![fmdb open]) {
	NSLog(@"No se pudo abrir la base de datos.");
	[pool release];
	return NULL;
 }
 return self;
}
 
-(NSMutableArray*) seleccionSQL:(NSString*)sql{
	FMResultSet *rs = [fmdb executeQuery:sql, @"hi'"];
	NSMutableArray  *datos = [[NSMutableArray alloc] init] ;	
    while ([rs next]) {
        // just print out what we've got in a number of formats.
       /* NSLog(@"%d %@ %@ %@ %@ %f %f",
              [rs intForColumn:@"c"],
              [rs stringForColumn:@"b"],
              [rs stringForColumn:@"a"],
              [rs stringForColumn:@"rowid"],
              [rs dateForColumn:@"d"],
              [rs doubleForColumn:@"d"],
              [rs doubleForColumn:@"e"]);
        */
 
        if (!([[rs columnNameForIndex:0] isEqualToString:@"idu"] &&
              [[rs columnNameForIndex:1] isEqualToString:@"usuario"])
			) {
            return 7;
        } else {
         NSString *cadena = [NSString  stringWithFormat:
         @"idu: %d usuario:%@ ",[rs intForColumn:@"idu"],
         [rs stringForColumn:@"usuario"]];
	 NSLog( cadena );
	NSMutableArray *aux = [[NSMutableArray alloc] init];
	[aux addObject:[NSString stringWithFormat:@"%d",
	[rs intForColumn:@"idu"]]];
	[aux addObject:[NSString stringWithFormat:@"%@",
	[rs stringForColumn:@"usuario"]]];
	[aux addObject:[NSString stringWithFormat:@"%@",
	[rs stringForColumn:@"email"]]];	
	[datos addObject:aux];
       }
    } 
    [rs close]; 
	return datos;
}
 
@end

Ahora que tenemos la base de datos debemos crear un controlador para asociar las acciones de la Interfaz al código fuente, algo sencillo como esto:

#import 
#import "bd.h"	
 
@interface controlador : NSObject {
	IBOutlet NSTableView *tablaDatos;
	bd *mi_bd;
}
- (IBAction)cargarDatos:(id)sender;
@end

esta es la implementación

#import "controlador.h"
#import "bd.h"
#import "personas.h"
 
@implementation controlador
- (IBAction)cargarDatos:(id)sender {
	if (!mi_bd){
	 mi_bd = [[bd alloc] init] ;
	 [mi_bd crearBD];
	}
	NSMutableArray *datos = [mi_bd seleccionSQL:
              @"select * from usuarios"];
	if (datos){
	 //Ahora vamos a meter los datos en el objeto
	personas *p = [[personas alloc] init];
	[p meterDatos:datos];
 	[tablaDatos setDataSource:p];
 
	}
}
@end

Como hace falta una clase que le de los datos a la vista de tabla (el datagrid) he creado una llamada personas:

#import 
 
@interface personas : NSObject {
	NSMutableArray *datos;
}
-(void) meterDatos:(NSMutableArray*)kdatos;
- (int)numberOfRowsInTableView:(NSTableView *)tableView;
- (id)tableView:(NSTableView *)tableView
 objectValueForTableColumn:(NSTableColumn *) tableColumn row:(int)row;
@end

esta clase implementa las acciones que necesita este tipo de objeto para representar la información y poder acceder a los datos…

#import "personas.h"
 
@implementation personas
-(void) meterDatos:(NSMutableArray*) kdatos {
  datos = kdatos;
}
- (int)numberOfRowsInTableView:(NSTableView *)tableView{
 return	[datos count];
}
 
- (id)tableView:(NSTableView *)tableView 
   objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row
{
	id value =nil; //creamos un objeto
	NSString *identifier =[tableColumn identifier]; 
	NSMutableArray *aux  =[datos objectAtIndex:row];
 
	/*creamos un objeto de la clase que queremos listar y 
          le asignamos el valor de la fila en la que estamos*/
	if ([identifier isEqual:@"id"])
		value=[aux objectAtIndex:0];
	else if ([identifier isEqual:@"nombre"])
		value=[aux objectAtIndex:1];
	else if([identifier isEqual:@"email"])
		value=[aux objectAtIndex:2];
	else NSLog("No encuentro el valor");
	NSLog([aux objectAtIndex:0]);
	return value;
}
 
@end

y básicamente ya lo tenemos todo, el main es una línea:

#import 
#import "bd.h"
 
#import "controlador.h"
 
int main(int argc, char *argv[])
{
 
    return NSApplicationMain(argc,  (const char **) argv);
}

footer
jbelon © | sitemap.xml