sábado, 10 de julio de 2010

iPhone, MonoTouch y la insoportable levedad del ser: El cliente (la prueba).

Ya decía ayer [iPhone, MonoTouch y la insoportable levedad del ser: El comienzo] que quería dejar constancia escrita (a modo de recordatorio para el futuro dentro de la saga Cuadernos del tolete [@ mi blog], también) de lo que observé al probar MonoTouch [Web oficial]. Y compararlo, en la medida de mis limitaciones, con las otras alternativas que he ido revisando: PhoneGap [Web oficial] y el propio Objective-C [@ Apple].

Lo primero que hay que saber es que desarrollar para iPhone exige trabajar sobre un Mac. Pese a que Mono [Web oficial] es multiplataforma, MonoTouch no. Bueno, sí pero no. Se puede desarrollar con Windows, creo, pero no sé cómo se resolvería la parte del simulador y, lo que es más importante, la gestión de los perfiles para «subir» el código a un terminal. Tampoco me he preocupado mucho en ese aspecto. Así que, si las cosas no han cambiado, se requiere igualmente usar un Mac para desarrollar con MonoTouch.

XCode 3 [@ Apple] no es santo de mi devoción. Así que he preferido usar MonoDevelop [Web oficial], ya que es «la herramienta definitiva» para el desarrollo .NET sobre Mac. Será también la que use para hacer un pequeño servicio Web correspondiente a la parte de servidor. Sin embargo, MonoTouch trabaja con la herramienta de diseño de interfaces de XCode, Interface Builder, y requiere el simulador de iPhone/iPad para poder ver el resultado. Por todo ello, se requiere instalar XCode con el SDK de iPhone.

Resumiendo, los pasos que vamos a necesitar dar para empezar serán:
  1. Tener un Mac. Si no es tu caso, hasta aquí «podemos leer».
  2. Descargar e instalar XCode + SDK de iPhone, para lo que debes estar dado de alta como desarrollador (gratis) en el iPhone Dev Center [Web principal].
  3. Descargar e instalar Mono para Mac [Download @ Mono Project].
  4. Descargar e instalar MonoDevelop para Mac [Download @ MonoDevelop].
  5. Descargar [Download Trial @ MonoTouch] e instalar [Installation @ MonoTouch] MonoTouch.

Cabe la opción de instalar Mono y MonoDevelop usando MacPorts [Homepage]. Yo lo hice [MacPorts y Postgresql 8.3]. Y me dio tantos problemas que tuve que eliminarlo todo y volver a instalar MacPorts. Desde entonces igual ya han resuelto los problemillas que tuve. Pero la instalación desde los paquetes oficiales es mucho más cómoda. Y, al menos en mi caso, no tengo intención de deinstalarlos en un futuro próximo. Se quedan.

Tan solo en el primer punto ya te vas a pegar un buen rato. XCode se distribuye con dos «sabores» (al menos): con y sin SDK de iPhone. Si, como fue mi caso, que ya tenía la variante «sin», cuando quieras «añadir» el SDK de iPhone vas a tener que descargar igualmente todo. Unos dos gigabytes y pico. Como dije, vas a tener para rato. Salvo que vivas en un país desarrollado y tengas una buena comunicación ADSL (o similar) a precios asumibles y competitivos. Yo sigo pagando treinta euros por una conexión que a duras penas llega a 1 Mb porque las operadoras pasan de actualizar el cableado de la zona. Para mí, descargar el SDK es cosa de horas.

MonoTouch es un producto de pago. Pero para desarrollar y probar sobre el simulador no tienes que gastarte ni un euro. Sin embargo, tendrás que registrarte igualmente para que puedas usar la versión trial.

Hubiera sido mucho más fácil —y también mucho menos divertido— hacer capturas y contar lo que hice para la prueba de concepto de la empresa —que, por cierto, el cliente al final decidió posponer el proyecto hasta un momento mejor; de la noche a la mañana la presión de tiempo desapareció—, pero la cláusula de privacidad —y la lealtad, principalmente— me impiden «abusar» de la confianza depositada sobre mí en ese aspecto. Así que he decidido, para esta serie de artículos, fabricar una aplicación tonta que me permita, eso sí, probar varias cosas. ¿Podría hacer algo, además, «comercialmente» interesante para un posible mercado objetivo?

Después de darle varias vueltas se me ocurrió que no hay nada más «universal» que la superstición (y su prima bastarda la charlatanería). Vaya, donde vaya, siempre encontrarás alguien dispuesto a creer que el zodiaco define el carácter de las personas: «¡Ah! ¡Que eres Tauro! Eso lo explica todo…». Pero seamos francos: ¿A cuánta gente conoces que no lea el horóscopo cuando cae en sus manos un periódico? No he preguntado «creer». He dicho «leer».

Rebusqué qué artes malignas arcanas se usan para la adivinación o establecimiento de tus dotes metafísicas y sobrenaturales y tropecé con la Numerología [@ Wikipedia]. Fácil de entender y fácil de calcular. Igual carencia de base científica. Perfecto para mis fines.

Sagan [@ Wikipedia | The Carl Sagan Portal] estará revolviéndose en su tumba. De verdad que lo hago con intenciones pedagógicas, nada más. De momento mi ánimo de lucro anda ridículamente desaparecido.

Al esperpento lo he llamado «Numerón dice». Y a continuación contaré algunas de las vicisitudes, y alegrías, que me ha dado programarlo usando MonoTouch. No voy a explicar las interioridades y particularidades del SDK de iPhone ni de Objective-C. En todo caso algún detalle concreto. Hay muy buenos tutoriales al respecto. Por ejemplo los que puedes encontrar en la sección al respecto en la Web de MonoTouch [Tutorials @ MonoTouch]. Sí es importantísimo, en cualquier caso, tener muy claro que todo el SDK de iPhone (la parte gráfica de CocoaTouch, al menos) se diseñó para respetar el patrón Modelo-Vista-Controlador [@ Wikipedia]. Hay que tenerlo presente porque toda «pantalla» que se vaya a diseñar —incluso cuando diseñemos conjuntos de controles complejos que pretendamos reutilizar— exigirá la creación de una clase controladora que será la responsable de decirle a la vista qué elementos ha de presentar y cuáles no. Pero lo dicho, no es este el lugar para dar una charla extensa al respecto. Hay multitud de tutoriales en Internet que explican eso mucho mejor de lo que yo podría hacerlo.

Hay que señalar que todos (o la mayoría) de esos tutoriales (y de los que encontraremos en Internet) se fundamentan en el uso de Interface Builder, que a su vez tiene su propio «lenguaje», con sus «outlets», sus «actions» y cómo se «enganchan» visualmente. Particularmente prefiero «hundirme en el fango» y programar «a pelo» si lo que busco es interiorizar las particularidades del SDK, pero a efectos prácticos, hacerlo con Interface Builder resulta más rápido y cómodo, pese a sus particularidades frente a otras herramientas de diseño de interfaces de usuario. Al principio cuesta un poco hacerse con las ideas inherentes a la construcción de interfaces de usuario usándolo, pero son tres o cuatro ideas básicas que rápidamente se pillan. Una vez aprehendidos los conceptos, las interfaces de usuario se sacan como churros. Lo «bueno» de usar Interface Builder es que será independiente del lenguaje. Te servirá tanto para Objective-C como para MonoTouch. Los conceptos son exactamente los mismos.

Pero empecemos. Si has trabajado con cualquier IDE (Eclipse, NetBeans, XCode, Visual Studio, etc.) sabrás que todos presentan una serie de principios comunes. Entre ellos está el que, cuando solicitas crear un nuevo proyecto, sobre todo cuando te permiten programar/hacer múltiples tipos de proyectos o usar varios lenguajes (hoy en día todos te lo permiten), te aparece una guía donde te deja elegir el tipo, lenguaje y dónde vas a almacenarlo. Para un proyecto iPhone usando MonoTouch —en nuestro caso pasaremos del iPad—, elegiremos el basado en el componente UINavigationController [@ Apple Developer], tal como muestra la siguiente captura. O sea C# --> iPhone and iPad --> iPhone Navigation-based Project.



Nada más pulsar sobre el botón de «Aceptar» para crear el proyecto, ya tienes un código ejecutable basado en una elemento UITableView [@ Apple Developer] y un UIViewController [@ Apple Developer] que implementa el protocolo UITableViewDataSource [@ Apple Developer], y que si se ejecuta (sobre el simulador), nos muestra la tabla con varias filas vacías. Las siguientes capturas presentan el árbol de proyecto recién creado y la primera ejecución inmediata.



Para el que no haya tenido aún un encuentro con Objective-C, sirva aclarar que el concepto equivalente más cercano de un «protocolo» de Objective-C en Java o .NET es el de «interface». La definición de un «contrato» que debe cumplir aquel que lo implemente. Existe, sin embargo, una salvedad y por ello resulta tan atractivo y fundamental en los frameworks de Objective-C. Permite que la implementación de parte de los métodos sea opcional. Resumiento, cuando en C# o en Java dices que una clase se acoge a una interfaz determinada, el compilador te obliga a implementar cada uno de los métodos definidos en el contrato de la interfaz. En Objective-C, únicamente te «obliga» a implementar aquellos métodos que el protocolo no haya definido como opcionales. Hasta la fecha, de los protocolos que he utilizado en mis pruebas, la gran mayoría es básicamente opcional. Por ejemplo, si revisamos la documentación del protocolo UITableViewDataSource comentado antes, de los once métodos que se definen, únicamente dos son obligatorios o requeridos. Esto ahorra escribir mucho código vacío. Si vas a trabajar con Objective-C.

En .NET, en particular en C#, la posibilidad de «tan sólo» implementar los métodos que se necesitan se consigue usando los delegados [@ MSDN Microsoft]. Y buena parte de la capa de adaptación implementada por MonoTouch para enganchar con el SDK de iPhone ha hecho esto mismo. Por ejemplo, el componente UIActionSheet [@ Apple Developer] requiere que se le indique un «delegado», el protocolo UIActionSheetDelegate [@ Apple Developer], que será el encargado de procesar la pulsación del botón que se elija. Generalmente esto implica que el controlador que lanza el action sheet en respuesta a algún evento del usuario, deberá pasarse a sí mismo (self, en Objective-C) como delegado y, por tanto, implementar (muchas veces «también») el protocolo UIActionSheetDelegate. Con MonoTouch podemos enganchar un método a una propiedad de tipo delegado definida en la clase UIActionSheet y de nombre «Clicked». El código resultante podría tener un aspecto como el siguiente:

_actionSheet.Clicked += (o,e) ==> { 
      if (e.ButtonIndex == 0) 
      { 
         ... 
      } 
   };


También se puede implementar un protocolo (que en MonoTouch te obliga a implementarlo todo, no hay nada opcional, porque en realidad se ha resuelto como una clase) o usar un delegado débil. Una explicación más detallada sobre cómo «enganchar» con el SDK lo puede encontrar en la explicación que dan del diseño del API en MonoTouch [API Design @ MonoTouch].

Dependiendo de las necesidades se tendrá que resolver conforme a alguna de las formas disponibles, pero resulta mucho más rápido, a mi modo de entender, hacer uso de los delegados. Siempre y cuando, claro, no tengas que devolver un valor. Los delegados únicamente sirven —en el contexto de MonoTouch— para métodos void.

Dentro de las cosas negativas encontradas, fue precisamente con UIActionSheet con lo que tuve un tropiezo que me hizo perder cosa de cinco o seis horas. Parece que no puedes lanzar un UIActionSheet dentro del método de respuesta a un evento haciendo uso (definiéndolo) como una variable local del método y enganchándole un delegado ad-hoc como el del código anterior. Revienta por todas partes. Fue un verdadero quebradero de cabeza hasta que encontré una discusión al respecto en los foros de MonoTouch [Trouble with Action Sheets @ MonoTouch Forums]. La solución pasa por definirlo como instancia de clase y crearlo durante la inicialización de la misma. Se deja inactivo hasta que se necesite mostrar. Esto me hizo restarle muchos puntos a MonoTouch.

Sigamos, que acabábamos de empezar y habíamos credo el proyecto y ejecutado el código por defecto.

Si pulsamos dos veces sobre los archivos que terminan en .xib se abrirá automáticamente el Interface Builder, lo que nos permitirá toquetear y diseñar la interfaz de usuario. La siguiente captura presenta el aspecto de los dos controladores que se crean automáticamente: MainWindow y RootViewController.



El primero —MainWindow— será el básico, el principal, el que hará de continente del resto. MainWindow es el controlador base, que hereda de/implementa el protocolo UIApplicationDelegate [@ Apple Developer]. En la captura se puede observar que ya «trae» un elemento visual que es el UINavigationItem [@ Apple Developer] que será gestionado por el NavigationController correspondiente.

Mientras tanto RootViewController, que por defecto es un controlador de una tabla, es el que primero se añade a la subvista definida en MainView. Yo me cargué directamente RootViewController y cree uno nuevo. Podía haberlo llamado como el que acababa de extinguir, pero lo abrevié. Cosas del directo.

Para crear un nuevo archivo .xib con el controlador, basta seguir las opciones Archivo --> Nuevo --> Archivo… y elegir



Esto nos creará un nuevo archivo .xib que podremos abrir con Interface Builder y nos mostrará una vista en blanco. A partir de ahí se podrá rellenar con lo que se quiera. En mi caso, este es el aspecto elegido:



Austero.

Aquí viene la parte de «enganche» entre la estructura generada por Interface Builder (el .xib) y el código C#. U Objective-C, pues el principio es exactamente el mismo. Interface Builder encapsula todo lo que pintemos en la vista o creemos directamente con él. Desde el exterior no se puede acceder a nada. Es una «caja negra» a todos los efectos. Salvo que le señalemos los «puntos de enganche». En el argot de Interface Builder esto se puede hacer usando «outlets» y «actions». Un «outlet» expone un componente visual al completo. Por ejemplo un UILabel [@ Apple Developer] o un UIButton [@ Apple Developer]. Lo que permite acceder a todas sus propiedades y métodos. Por su parte, un «action» facilita abstraer qué elemento lanza la acción.

No basta con definirlos, también hay que asociarlos visualmente. Pero eso ya lo dejo para que se busque en el tutorial que corresponda. En mi caso opté por outlets y «publiqué» los elementos de interacción de la interfaz. En mi caso, además, preferí darle un «tipado fuerte» desde el propio Interface Builder indicando la clase que debe admitir ese outlet. Objective-C ofrece un tipo débil genérico, «id», que se emplea para pasar elementos que pueden ser de distinto tipo. Es la base, por ejemplo, para gestionar las colecciones (arrays, diccionarios, etc).



Esto me permitió asociar delegados a los botones, por ejemplo, en el evento ViewDidLoad. Dicho evento se ejecuta justo cuando se concluye la deserialización de un archivo .xib, en tiempo de ejecución, y se desea finalizar la inicialización de los elementos que lo componen. Tal como muestra el siguiente código:

public override void ViewDidLoad ()
{
   base.ViewDidLoad ();
  
   this.nombreTextEdit.EditingDidEndOnExit += this.DidEndOnExit;
   this.apellidosTextEdit.EditingDidEndOnExit += this.DidEndOnExit;
   this.nacimientoButton.TouchDown += this.TouchButtonNaciste;
   this.sexoSegmentedControl.ValueChanged += this.SexoChanged;
   this.diaFuturoButton.TouchDown += this.TouchButtonDiaFuturo;
   this.miFuturoButton.TouchDown += this.MiFuturoAUnClick;
}

La introducción de las fechas las monté usando Action Sheets, que se lanzan cuando se pulsa sobre el botón que tienen los textos solicitando las fechas. La siguiente captura muestra la aplicación en ejecución y lo que se muestra al pulsar el botón «Dime en qué fecha naciste».



Colocar un UIDatePicker [@ Apple Developer] dentro de un UIActionSheet tiene algo de miga. El truco lo dejo para que lo busquen en Internet.

Otra de las cosas que me quitó mucho tiempo fue «esconder» el teclado cuando terminaba de introducir el nombre y el apellido. Por más que miré y remiré no encontré una propiedad del componente UITextField [@ Apple Developer] que me facilitara el saltar al siguiente control o a esconderlo tan pronto terminase de escribir. Al final tuve que recurrir a una artimaña por código. Y por lo que he visto, esto es así también en Objective-C. La «culpa», en este caso, no es de MonoTouch. La «solución» pasa por crear un delegado y añadirlo al evento de UITextField, EditingDidEndOnExit, tal como se muestra a continuación.

this.nombreTextEdit.EditingDidEndOnExit += this.DidEndOnExit;

Este delegado no existe como tal en la clase UITextField del SDK de iPhone. A la hora de diseñar el API de MonoTouch, y su enlace cono el SDK de iPhone, se optó por añadir algunos «extras», como que la clase UITextField tenga ese evento. En Objective-C se tendría que resolver implementando el protocolo UITextFieldDelegate [@ Apple Developer] y decirle al text field en cuestión cuál es el delegado responsable de responder a ese método. Varios puntos positivos para MonoTouch por esta iniciativa.

En el código del delegado debemos retirar el foco (first responder) del elemento para que se oculte automáticamente el teclado invocando el método ResignFirstResponder.

void DidEndOnExit (object sender, System.EventArgs args) 
   {
      UITextField tf = (sender as UITextField);
      if ( null != tf ) {
         this.ResignFirstResponder();
      }
   }

Una de las cosas que me encanta de usar C#, tanto para el desarrollo en general como para desarrollar para iPhone en particular, es la posibilidad de usar la estructura genérica Nullable [@ MSDN Microsoft] y simplificar las preguntas cuando se tratan de tipos o estructuras que no admiten estados nulos, como int o DateTime (la estructura de C# para las fechas). De esta forma, las condiciones se simplifican. El código que lanza la visualización del resultado podría estar condicionada a introducir todos los datos y tener un aspecto como el siguiente:

void MiFuturoAUnClick (object sender, EventArgs args )
{
   // Comprobamos que estan todos los datos
   string nombre = this.nombreTextEdit.Text.Trim ();
   string apellidos = this.apellidosTextEdit.Text.Trim ();
   Nullable<int> sexo = this.sexoSegmentedControl.SelectedSegment;
   Nullable<DateTime> diaNacimiento = null;
   Nullable<DateTime> diaFuturo = null;

   nombre = ( nombre == string.Empty ) ? null : nombre;
   apellidos = ( apellidos == string.Empty ) ? null : apellidos;
   diaNacimiento = fechaElegidaNacimiento ();
   diaFuturo = fechaElegidaFuturo ();

   if ( ( nombre == null ) || ( apellidos == null ) || ( _diaNacimiento == null )
            || ( _diaFuturo == null ) || ( sexo == null ) ) 
   {
      new UIAlertView ( "¡Faltan datos!", "Faltan datos para poder hacer un buen calculo de tu proximo futuro", 
               null, null, "Pues muy bien" )
         .Show();
   }
   else
   {
      this.NavigationController.PushViewController ( new NumeronDice( CalcularNumero () ), true );
   }
}

En el anterior código se puede observar que, en caso de tener alguna de las entradas de datos a null, se lanzará una alerta con un mensaje indicando que no está todo. Para eso se emplea UIAlertView [@ Apple Developer]. Este componente del SDK es similar a los Action Sheet y en este caso no se ha asignado ni delegado (protocolo Objective-C) ni delegado (evento C#) para responder a los eventos de pulsación sobre los botones. Únicamente se usa para informar que no tenemos todos los datos y solo presenta un botón para salir del mensaje.



La otra cosa interesante a reseñar, dentro del código anterior, es el método PushViewController de la clase UINavigationController. Cuando empleamos un esquema de navegación usando UINavigationController, el SDK de iPhone ya gestiona muchas de las cosas comunes, como mostrar un botón de vuelta atrás con el nombre de la «pantalla» que acabamos de dejar. Para ello lo gestiona usando una pila de controladores. Cuando queremos que aparezca una nueva «pantalla» sobre la anterior, pero que se ofrezca la posibilidad de volver (sin intervención de código) a la anterior, se apilará (push) sobre la que esté activa en ese momento.

A continuación un adelanto, a modo de captura, de cómo podría ser la vista con los resultados.




Pero su acabado definitivo lo dejaré para la siguiente entrega de esta fascinante serie. Será cuando se integre con el servicio web creado para conocer el futuro de todo aquel que esté interesado. No te pierdas, por tanto, el próximo capítulo. Pronto, pronto.

De momento decir que, si Apple entra en razón, MonoTouch es una herramienta con un valor incalculable para agilizar el desarrollo de aplicaciones para iPhone, iPad e iPod Touch. Únicamente quedaría saber qué tal se desenvuelve el rendimiento en estos terminales. Algo que, me temo, no llegaré a saber a ciencia cierta hasta que decida comprar la licencia.

No hay comentarios: