VISITAS:

lunes, 17 de agosto de 2015

iOS (I) Autosizing Masks

En primer lugar quiero aclarar que las autosizing masks son el método "antiguo" de crear layouts multi-tamaño en iOS. Apple sacó los autolayout constraints posteriormente y es lo que debería utilizarse en lugar de las autosizing masks. Sin embargo, en mi opinión, es importante conocer y practicar las autosizing masks antes de meterse con autolayouts. Los autolayouts son mucho más genéricos, permiten definir prácticamente todo de forma gráfica y soportan casi todos los tamaños y orientaciones de pantalla.
Podríamos pensar que lo ideal es empezar a aprender autolayouts, ya que es "lo último", y las autosizing masks están "deprecated" por Apple. Y en parte eso sería lo correcto. Pero... yo he intentado aprender los autolayouts sin conocer previamente autosizing masks, y me ha resultado muy muy complicado. Por eso, recomiendo empezar por aquí, a pesar de que tenemos que ser conscientes que quizás este esfuerzo no valga para hacer muchas aplicaciones en el futuro. Para programadores que vienen de versiones de iOS anteriores y que ya conocen autosizing masks, pueden ir directamente a aprender los autolayouts sin problemas. Y por supuesto, como recomendación final antes de meternos en harina: para hacer aplicaciones, utilizar autolayouts constraints, sin duda.

Struts y Springs

Las autoresizing masks están basadas en los conceptos de layoyut "struts y springs"  (Ojo, no viene de los frameworks para aplicaciones web struts y spring).
Se define cómo cambia el tamaño y los márgenes de una vista cuando cambia el tamaño de la vista padre. O sea, para cada vista definimos qué márgenes (top, left, bottom, right) pueden cambiar y qué tamaño (horizontal, vertical) puede cambiar cuando la vista padre cambia de tamaño. Al ancho y alto se les llama springs, y a los márgenes se les llama struts.
Para cada margen o dimensión podemos definir si es fija o flexible, Si es fija se quedará como está cuando la vista padre cambie de tamaño, si es flexible cambiará de tamaño con el padre.
Cada restricción es una máscara (6 en total) y se combinan mediante OR para definir el comportamiento de cada vista ante los cambios de tamaño.
Estas son las máscaras:

  • UIViewAutoresizingFlexibleBottomMargin: permite que cambie la distancia entre el borde inferior de la vista y el borde inferior del padre.





  • UIViewAutoresizingFlexibleTopMargin: permite que cambie la distancia entre el borde superior de la vista y el borde superior del padre.




  • UIViewAutoresizingFlexibleLeftMargin: permite que cambie la distancia entre el borde izquierdo de la vista y el borde izquierdo del padre.




  • UIViewAutoresizingFlexibleRightMargin: permite que cambie la distancia entre el borde derecho de la vista y el borde derecho del padre.




  • UIViewAutoresizingFlexibleHeight: permite que cambie el alto de la vista cambie cuando cambie el alto de la vista padre.
  • UIViewAutoresizingFlexibleWidth: permite que cambie el ancho de la vista cambie cuando cambie el ancho de la vista padre.
Combinando estas máscaras con el operador de bit OR | se define el comportamiento de cada vista ante cambios en la vista padre.
NOTA: Cuando hablo de cambios en la vista padre, no me refiero a que en una aplicación la vista padre vaya a estar cambiando de tamaño (Normalmente, una vez arrancada la aplicación, las vistas no cambian su tamaño). Me refiero a que a partir del diseño con Interface Builder (Xcode), los tamaños de pantalla en los que va a correr nuestra aplicación cambiarán, y es a esos cambios a los que me refiero, a cambios con respecto al diseño de la aplicación. Por ejemplo, nosotros podríamos haber diseñado nuestra aplicación para orientación portrait, pero el usuario la ejecuta en modo landscape; entonces la vista principal cambia su tamaño (ancho > alto, y otros tamaños) y por tanto todas las vistas hijas recursivamente.

Ejemplo

Por ejemplo, imaginemos una vista de dimensiones (10, 10, 50, 50) (x,y,w,h) dentro de otra vista de dimensiones (0, 0, 70, 70).



A esta vista le ponemos la siguiente máscara:

    UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth

De esta forma, sea cual sea el tamaño de la vista padre, la distancia de los bordes a la vista padre será siempre 10 puntos. O sea, si la vista padre aumenta su ancho al doble, o sea 140 puntos, el resultado sería el siguiente:

Y si lo que aumenta es el alto de la vista padre:

Como se ve, al cambiar el tamaño de la vista padre, la distancia de los bordes de la vista a los bordes de la vista padre no cambian, ya que no hemos definido para esta vista ninguna máscara de flexibilidad en los bordes. Lo que sí hemos hecho flexible son el ancho y el alto, y por tanto, eso es lo que se redimensiona al cambiar el tamaño de la vista padre.
Igualmente, si el tamaño de la vista padre se redujera, la vista reduciría su tamaño, pero no cambiarían los márgenes, es decir, la distancia de los bordes.
Hay que tener en cuenta que cuando no se define una máscara, entonces esa máscara es fija, no flexible,

Otro ejemplo

Pongamos ahora la siguiente máscara a la vista anterior:

    UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleRightMargin

En este segundo caso, hacemos flexible los márgenes inferior y derecho (o sea, la distancia de los bordes inferior y derecho a los mismos márgenes de la vista padre). El resto de márgenes y las dimensiones son fijas y no pueden cambiar.
Si en este caso la vista padre aumenta al doble su tamaño, tanto ancho como alto, tendremos:

Como se ve, las dimensiones de la vista no cambian. Tampoco cambian los márgenes izquierdo y superior. Lo que sí es flexible, y de hecho cambia son los márgenes derecho e inferior.

Autosizing masks en Xcode

Quizás el lector esté pensando ahora mismo cómo puede probar las autosizing masks, ya que Apple las ha sustituido por autolayout constraints. Sin embargo, Apple ha dejado una opción en los proyectos para poder hacer el layout de nuestras escenas utilizando autosizing masks. Lo más probable es que Apple ha dejado esta opción para mantener la compatibilidad con proyectos antiguos que utilizaban esta técnica, pero ahora mismo nos viene bien para poder practicarla y aprender.
Creemos un nuevo proyecto con Xcode, iOS -> Application -> Single View Application. Abrir Main.storyboard
En el panel de Utilities (activarlo con el icono de la derecha)
seleccionar el File inspector (el icono de la izquierda):

En la sección Interface builder document, desactivar las opciones:
  • Use Auto Layout
  • Use Size Classes
Con estas dos opciones desactivadas, nuestro storyboard funcionará con el sistema antiguo de autosizing masks.
Cuando intentemos desactivar la opción Use Auto Layout, Xcode nos pedirá confirmación y además nos indica que también se desactivará la opción Use Size Classes. Y más aún, al no tener size classes, Xcode necesita saber para qué tamaño/dispositivo vamos a hacer el diseño, iPhone o iPad (size classes permite definir el layout para múltiples dispositivos, como se verá en otro artículo). Seleccionemos por ejemplo la opción iPhone.
Ahora ya tenemos nuestro proyecto preparado para trabajar con autosizing masks, en lugar de los modernos autolayout constraints.

En la parte inferior del panel de utilities, seleccionar la object library (el tercer icono):

Buscar el objeto View (Represents a rectangular region in which it draws and receives events) y arrastrarlo a la vista. Lo primero que haremos será cambiarle el color para poder ver cómo cambia y darle una posición y tamaño dentro de la vista padre.
Para cambiarle el color, seleccionar el cuarto icono:
y después cambiar Background.
Para situarlo y darle tamaño, seleccionar el quinto icono. El iPhone 5 tiene un tamaño de 320x568 puntos, así que a nuestra vista le daremos un tamaño algo más pequeño, por ej. (10, 10, 300, 500).
En la sección de autoresizing, quitar todas las máscaras. Por defecto, aparecerán todas activadas (en rojo en los bordes y dentro del recuadro). Para desactivarlas, pulsar sobre cada una de las 6 para que queden en un color muy clarito.
Vamos a previsualizar nuestra vista con distintos tamaños de la vista padre (el dispositivo en este caso). Para ello, tenemos la opción clásica de arrancar el simulador y ejecutar con distintos tipos de dispositivos. Pero esta opción es bastante engorrosa y lenta, ya que hay que cambiar el dispositivo, compilar, y ejecutar la aplicación. Xcode tiene una función de previsualización de una escena en el mismo Interface Builder, sin tener que arrancar el simulador. Veamos cómo hacerlo.
En primer lugar, abrir el asistente (el segundo icono con dos círculos):
Normalmente aparecerá el código swift del controlador asociado a la vista. Pero esto no es lo que queremos. En la parte de arriba de la ventana de código swift, tenemos una especie de pestaña con dos círculos y la etiqueta Automatic. Pulsar esa pestaña y en el menú que aparece, seleccionar Preview. Esto nos cambiará la vista del asistente a una previsualización. En la parte inferior izquierda hay un botón + para añadir nuevas previsualizaciones (nótese que son previsualizaciones de iPhone, no de iPad, ya que al desactivar size classes, hemos elegido el iPhone como dispositivo para nuestra aplicación). Cuando tengamos varias previsualizaciones es muy útil poder moverse por esta pantalla (nótese que no hay barras de scroll). Para ello, se utiliza la rueda del ratón (scroll hacia arriba y hacia abajo) y tecla SHIFT más rueda del ratón (scroll horizontal, izquierda y derecha). Si pulsamos sobre una de las previsualizaciones, podemos borrarla pulsando la tecla de borrar. Además, en cada previsualización podemos ver su versión landscape. Para ello, seleccionamos una previsualización (excepto la primera, que no se puede ni borrar ni rotar) y en la parte de abajo de la previsualización tenemos un botón que permite rotar el dispositivo.

Una vez que sabemos manejar el asistente de previsualizaciones, estamos en condiciones de ver cómo se comporta nuestra vista cuando por ejemplo, rotamos el teléfono. Y como podremos ver, el resultado es desastroso (si hemos quitado todas las autosizing masks). Pero podemos arreglarlo fácilmente: seleccionamos nuestra vista (no la previsualización), elegimos el inspector de tamaños (size inspector) y activamos las dos máscaras internas (width y height) en la sección de autoresizing. Sólo con estos dos cambios, nuestra vista crecerá y decrecerá para adaptarse al tamaño del dispositivo, pero manteniendo los márgenes fijos.

Ahora es el momento de que el lector se ponga manos a la obra y practique con distintos layouts, colocando varias vistas, unas dentro de otras y probando distintas máscaras. Éste es un ejercicio muy útil y que recomiendo (al menos durante una hora o dos) para hacerse con esta técnica (que vuelvo a insistir, está obsoleta hoy). Si el lector se plantea algunos problemas un poco complejos se dará cuenta de que hay algunas posibilidades de layout que son imposibles de llevar a cabo con autoresizing masks. En estos casos sería necesario meter código que trate los eventos de cambio de orientación y cambiar los tamaños y posiciones (esto es algo muy engorroso y que se resuelve con autolayouts, como veremos en otro artículo).

Ejercicio

Aquí propongo a modo de ejercicio, un layout para que el lector practique, aunque creo que es bueno intentar imaginar una aplicación y diseñar su layout con Xcode utilizando autoresizing masks.



Para terminar

Una vez más insisto que las autosizing masks no es la técnica recomendada por Apple hoy día (2015), sino los autolayouts junto con sizing classes.
Pero, ¿cuál es el motivo por el que Apple decidió dejar de utilizar autosizing masks? Realmente, los motivos de Apple no siempre son claros, pero en este caso sí hay razones técnicas que permiten explicar el cambio. Hasta hace poco, no era difícil diseñar una aplicación que funcionara en todos los dispositivos iOS existentes, simplemente había que pensar en iPhone portrait y landscape, iPad portrait y landscape. En total 4 posibilidades. Lo que solían hacer los desarrolladores era hacer 4 storyboards, uno para cada una de estas posibilidades.
Cuando Apple sacó los nuevos modelos de iPhone, el 6 y sobre todo el 6+, el espectro de posibilidades empezó a aumentar, y el futuro nos depara algunas otras posibilidades (por ejemplo, el iPad Pro, que parece que está al caer, aunque ahora mismo son sólo rumores). Esto podría complicar muchísimo el diseño de aplicaciones. Así que parece que Apple decidió abordar definitivamente el problema de múltiples tamaños y resoluciones en los dispositivos (cosa que por cierto, Google abordó desde el principio con Android).
El problema de autosizing masks es que no soporta todas las posibilidades de layout (no es difícil plantearse un ejemplo imposible de realizar), al menos con Interface Builder directamente (sería necesario resolver algunos temas por código). Otro problema es que para cada posibilidad (iPhone, iPad, portrait, landscape, etc) tenemos que hacer un diseño especial (no para casos sencillos, pero sí en la mayoría de los casos).
Para resolver todos estos problemas, Apple "inventó" los autolayouts y los size classes que veremos en un artículo posterior.







martes, 11 de agosto de 2015

SWIFT (VI) FUNCIONES Y CLOSURES

Las funciones son algo típico en todos los lenguajes de programación clásicos. Los closures ya no lo son tanto, al menos, no son tan conocidos, aunque llevan existiendo muchos años en lenguajes tan conocidos como Javascript.

FUNCIONES EN SWIFT

Una función es un bloque de código que puede ser invocado por su nombre para ejecutar una determinada tarea. Puede recibir datos de entrada y puede retornar uno o más resultados. Cuando se define una función se especifican los parámetros de entrada y el tipo del valor retornado (si los hay). Cuando se invoca a una función se pasan datos como argumentos a la función y se puede recoger el resultado en una variable, por ejemplo. Nótese la sutil diferencia entre parámetros y argumentos.

Ejemplo de declaración de una función en swift:

    func suma(operando1 : Int, operando2 : Int) -> Int {
        return operando1 + operando2
    }

Llamada a una función:

    var a : Int = suma(3, 6)

Parámetros externos

En el ejemplo anterior, los nombres de los parámetros son internos a la función y no pueden utilizarse en la llamada, es decir, la llamada se basa únicamente en el orden de los parámetros. Pero a veces es mejor (o más claro) que en la llamada a la función se especifique también el nombre de los parámetros. Para ello se antepone el carácter # al nombre del parámetro:

    func resta(#minuendo : Int, #sustraendo : Int) -> Int {
        return minuendo - sustraendo
    }

Y la llamada:

    var resultado : Int = resta( minuendo : 5, sustraendo : 3)

Parámetros por defecto

Swift permite especificar un valor por defecto para un parámetro (o parámetros) que pudieran no especificarse en la llamada a función. Estos parámetros con valores por defecto deben situarse al final de la lista de parámetros en la declaración de la función. Cuando en un parámetro se especifica un valor por defecto, swift asume que ese parámetro es externo y permite su nombrado en la llamada (ver subapartado anterior "parámetros externos").

    func info(account : Int, owner : String  = "Unknown") -> String {
        return "Account: \(account), owner: \(owner)."
    }

Ejemplos de posibles llamadas a esta función:

    var s1 = info(26351)  --> Account 26351, owner: Unknown.
    var s2 = info(10005, owner : "John")  --> Account 10005, owner: John.

Retornando múltiples valores desde una función

Una función puede retornar múltiples valores de varias formas: retornando un objeto, retornando un array o diccionario, y por último, retornando una tupla. Veamos este último caso.

    func getUserInfo(id : String) -> (nombre : String, apellido : String, dni : Int) {
        // buscar información del usuario en BD por ejemplo
        return ("Juan", "López", "4747563-P")
    }

Llamada a esta función:

    var info = getUserInfo("A3625")
    println("\(info,nombre) \(info,apellido) - \(info.dni)")

Número variable de parámetros

En swift una función puede recibir un número variable de parámetros (puede ser cero). Esto se indica con ...
Dentro de la función los parámetros son un array.

    func printInfo(datos : String...) {
        for s in datos {
            println(s)
        }
    }

Llamada:

    printInfo("a", "b", "c", "d")

Parámetros variables

Los parámetros recibidos por una función son constantes (es como si tuvieran un let delante) y no se pueden modificar dentro de la función.
Si queremos modificar alguno de los parámetros de una función, debemos declararlo variable poniéndole var delante.

    func suma(var operando1 : Int, operando2 : Int) -> Int {
        operando1 += operando2
        return operando1
    }

Esto no significa que el argumento pasado a la función se vaya a modificar, simplemente que podemos cambiar el valor internamente en la función. Para que se modifique el argumento externo, tendremos que utilizar parámetros de entrada/salida, como se describe en el siguiente subapartado.

Parámetros de entrada/salida

Para que una función pueda modificar el valor de un argumento es necesario definir el parámetro correspondiente como inout:

    func duplica(inout valor : Int) {
        valor = 2 * valor
    }

En la llamada tenemos que anteponer el carácter & al argumento (el cual tiene que ser una variable obligatoriamente):

    var v : Int = 4;
    duplica(&v)
    println(v)   --> 8

CLOSURES EN SWIFT

Antes de entrar en las closures, introduzcamos el tema 

Las funciones como tipos de datos

Una característica muy interesante de swift (y de otros lenguajes de programación) es que las funciones pueden ser tratadas como tipos de datos. De esta forma, se puede por ejemplo definir una función y asignarla a una constante o variable:

    func kilometers2Miles(kilometers : Float) -> Float {
        return meters * 0.62137
    }
    let f = kilometers2Miles

Habiendo hecho esa asignación podemos invocar a la función a través de la constante:

    var result = f(100.0)

Lo interesante de esto es que cuando asignamos una función a una constante tenemos todas las capacidades de los tipos de datos. Concretamente, una función puede pasarse como argumento a otra función, o incluso ser retornada de una función.

El tipo de datos de la función lo definen los parámetros y el valor retornado. En el ejemplo anterior, la función recibe un parámetro Float y devuelve un valor Float, por tanto el tipo de esta función sería:

    (Float) -> Float

Por ejemplo, podemos definir una función que reciba otra función como parámetro:

    func invoke( theFunction : (Float) -> Float, value : Float) -> Float {
        return theFunction(value)
    }

Y podemos invocarla así:

    invoke(kilometers2Miles, 34.8)

Igualmente, podemos retornar una función:

    func getFunction() -> (Float) -> Float {
        return kilometers2Miles
    }

Expresiones closure

Una expresión closure es sencillamente un bloque de código. Las expresiones closure se pueden asignar a constantes o variables:

    let f = { println("Hola") }
    f()   --> Hola

Las expresiones closure también pueden recibir parámetros y devolver resultados. Por ejemplo:

    let m = { ( operando1 : Float, operando2 : Float) -> Float in return operando1 * operando2 }
    let v = m(20, 34.2)

La sintaxis es parecida a la de una función, pero no tiene nombre, todo va entre llaves y se utiliza la palabra reservada in para indicar el comienzo del código. De hecho, las funciones swift son expresiones closure con nombre.

Closures

Un closure es un bloque de código (una función o una expresión closure) junto con una o más variables que existen en el contexto de ese bloque de código. Por ejemplo:

    func externa() -> () -> Int {
        var contador = 0
        func interna() -> Int {
            contador += 1
            return contador
        }
        return interna
    }

    let miClosure = externa()
    let resultado = miClosure()

En este ejemplo, la función externa retorna una función interna. Realmente retorna un closure, ya que retorna la función interna más la variable contador, que está declarada en el scope de la función interna. De hecho, cada vez que se llama a miClosure() se va incrementando el contador.












domingo, 9 de agosto de 2015

SWIFT (III) TYPE CAST Y CHEQUEO DE TIPOS

TYPE CASTING

Hay dos tipos de conversión de tipos en swift:
  • upcasting: se trata de convertir un objeto de una clase a otra clase padre (es decir, subir hacia arriba en la jerarquía)
  • downcasting: se trata de convertir un objeto de una clase a otra clase derivada (es decir, bajar en la jerarquía)
El upcasting es siempre seguro (ya que siempre podemos convertir un objeto de una clase a una de sus clases padre) y el compilador lo deja hacer sin problemas. Ejemplo de upcasting:

        var b : UIButton = UIButton()
        var c : UIControl = b as UIControl

En este ejemplo, como UIControl es una clase padre de UIButton, podemos hacer tranquilamente el casting (upcasting) de UIButton a UIControl.

Sin embargo, el downcasting es responsabilidad del programador, es decir, el compilador no puede asegurarlo, y por tanto, el lenguaje swift tiene una syntaxis especial para el downcasting. Cuando se hace downcasting utilizando as, el compilador da un error avisando que no puede convertir el tipo:

        var c : UIControl = UIButton()
        var b : UIButton = c as UIButton    --> error de compilación

La primera sentencia es correcta, sin embargo, la segunda dará un error de compilación advirtiendo que no puede convertir los tipos de forma segura. Ese error no significa que se esté haciendo algo incorrecto, sólo significa que el compilador no puede asegurar la conversión.

El downcasting en swift se hace utilizando un casting forzado con as!

        var c : UIControl = UIButton()
        var b : UIButton = c as! UIButton   --> correcto

En este caso se deja al programador la responsabilidad de que c sea de la clase UIButton o una de sus clases derivadas. Si no fuera así, se elevaría una excepción en runtime (ver más abajo cómo chequear el tipo de una variable).

Existe una tercera forma de casting en swift: optional binding. Para ello se emplea as?

En este caso, la conversión retorna un optional, y si no es posible la conversión, retornará nil. Por ejemplo:

        var b : UIButton? = c as? UIButton   --> si c no puede convertirse a UIButton, entonces el resultado es nil, lo cual es válido porque la variable b es optional

Igualmente podemos emplear esta técnica en un if como se hace con los tipos optional:

        if let b = c as? UIButton {
                b puede utilizarse como un UIButton
        }

TYPE CHECKING

Además del casting, en swift es posible chequear el tipo de una variable:

        if b is UIButton {
                // b es una instancia de UIButton y por tanto podemos utilizarlo como tal
                c = b as! UIButton   --> esto va a funcionar
        }

Combinando todas estas técnicas podemos manipular el tipo de las variables dentro de su jerarquía.













SWIFT (II) TIPOS OPTIONAL

Swift es un lenguaje de programación moderno que utiliza las mejores técnicas de los lenguajes de programación que han tenido más éxito en los últimos años: Java, Objective-C, Python, etc.
Este nuevo lenguaje de programación "inventado" por Apple para que sea su lenguaje del futuro en plataformas iOS y OSX tiene muchísimas ventajas, porque ha cogido lo mejor de cada lenguaje, convirtiéndose hoy en día (2015) en el lenguaje más seguro y potente que existe.
Sin embargo, hay ciertas características de swift que no han sido "copiadas" de otros lenguajes y que suponen una barrera de entrada al lenguaje, incluso para programadores muy experimentados. Una de esas barreras son los tipos optional, algo que bloquea a muchos programadores cuando empiezan con swift (conozco a algún programador que empezó con mucha ilusión a aprender swift y cuando se encontró con los tipos optional, después de dar muchas vueltas al tema, no consiguió entenderlo y abandonó el lenguaje "para otro momento").
Voy a intentar explicar (en español y con ejemplos) los tipos optional de swift para que no sean una barrera para nadie. Supongo que se me quedarán algunas cosas en el tintero, y también supongo que cuando el lector pase por este blog, el lenguaje swift habrá cambiado bastante y que algunas de las cosas que aquí expongo podrían haber cambiado (prometo mantener esta entrada al día en la medida de lo posible). 

TIPOS OPTIONAL

Los lenguajes de programación permiten expresar la situación en la que una variable no contiene ningún valor. Esto es muy útil, por ejemplo, cuando queremos retornar nil (o null) desde una función indicando que no se retorna nada. También es útil para indicar que una variable todavía no se ha inicializado. En lenguajes como C++ o Java, para indicar que una variable no contiene ningún valor, se asigna el valor nil a la variable. Esta solución funciona para variables de tipo objeto, pero no para los tipos básicos: int, double, boolean, etc. En estos casos teníamos que asignar valores especiales para indicar que la variable no tiene valor, por ejemplo -1. Swift resuelve este problema de manera general para todos los tipos mediante los tipos optional.

En Swift se definen los tipos optional añadiendo el carácter ? al final del tipo. Ejemplos:

        Int?
        String?
        MiClase?
        etc

En Swift, una variable de un tipo optional puede contener un valor (básico u objeto, según el tipo) o bien no contener ningún valor. Sólo se puede asignar nil a una variable de un tipo optional. A una variable que no sea optional (sin el carácter ?), no se le puede asignar nil, ya que el compilador daría error:

        var i : Int? = nil
        var j : Int = nil   --> error de compilación

Igualmente si una variable es de tipo optional, no podemos asignarla a otra variable que no sea optional (incluso aunque contenga un valor).

        var k : Int? = 7
        var m : Int = k   --> error de compilación

Tenemos que pensar que los tipos Int e Int? no son lo mismo. El tipo Int? es un wrapper de Int, es decir, una especie de objeto que contiene un Int, por tanto, no podemos asignar un Int? a un Int. Sin embargo, sí es posible asignar un Int a un Int?, ya que el compilador hace las comversiones adecuadas. En el siguiente apartado veremos cómo asignar una variable Int? a otra de tipo Int.

En resumen, cualquier tipo Swift puede ser optional, tanto los objetos como los tipos básicos. Y sólo podremos asignar nil a una variable de un tipo optional.

FORCED UNWRAPPING

Forced unwrapping es el mecanismo que proporciona swift para asignar una variable de un tipo optional a una variable no optional. Forced unwrapping permite obtener la variable interna (si existe) que tiene el wrapper optional. Por ejemplo:

        var a : Int? = 5
        var b : Int = a!

Añadiendo el carácter ! al final de la variable se hace un forced unwrapping de la variable optional, es decir, se extrae el valor de la variable si existe (si la variable no contuviera un valor, o sea nil, se produciría una excepción en runtime).

Cuando se va a hacer forced unwrapping Para comprobar si el valor de una variable es conveniente comprobar primero si la variable optional es distinta de nil, es decir, si tiene un valor. Para ello se compara utilizando los operadores == y != (habituales en lenguajes de programación tipo C). Si hemos comprobado que una variable es != nil porque hemos hecho un if previamente, entonces podríamos leer su valor con tranquilidad:

        if a != nil {
                b = a!
                print("Se puede leer el valor de a = \(a!) sin problemas")
        } else {
                print("La variable a no contiene ningún valor")
        }

OPTIONAL BINDING

Optional binding es una técnica de swift que permite, al mismo tiempo que se comprueba que una variable optional tiene un valor, asignar ese valor a otra variable (o constante) para utilizarla después sin necesidad de forced unwrapping.

La sintaxis del optional binding es:

        var a : Int? = 4
        if let b = a {
                print("Se puede leer el valor de b = \(b) sin problemas")
        } else {
                print("La variable a no contiene ningún valor")
        }

Se pueden utilizar constantes (let) o variables (var) en optional binding, dependiendo de si el valor se va a modificar después o no.

También se pueden crear varias variables y constantes en un optional binding:

        if let a1 = a, b1 = b {

IMPLICIT UNWRAPPED OPTIONALS

Algunas veces, tenemos claro que una variable optional va a tener siempre un valor (es decir, el programador sabe que esa variable siempre va a contener un valor). En estos casos puede ser útil indicar en la declaración este hecho. Para ello, se utiliza la siguiente sintaxis:

        var i : Int!  --> se inicializa a nil
        i = 5

Este tipo de optionals se llaman implicit unwrapped optionals, indicando que la variable es optional pero el compilador va a hacer un unwrapped de forma implícita cada vez que se utilice. Por ejemplo:

        var j = i   --> no hace falta forced unwrapping

Lo primero que nos preguntamos tras leer estas líneas es: Y entonces, ¿por qué no declaramos la variable como no optional? Es la pregunta obvia después de la definición de implicit unwrapped optional. Veamos un caso útil en un constructor de una clase:

        class Ciudad {
                var nombre : String!    --> se inicializa a nil, por tanto tiene que ser optional
                init( nombreCiudad:String) {
                        self.nombre = nombreCiudad   --> se le asigna un valor y ya nunca va a ser nil
                }
        }

A partir de ese momento, la variable city.nombre se puede utilizar normalmente, sin hacer forced unwrapping. Es como si dejáramos en la responsabilidad del programador asegurar que una variable nunca va a ser nil (aunque para el compilador, sí podemos asignarle un valor nil).

Alguien podría decir ahora: "Yo tengo una solución más sencilla a este caso"

        class Ciudad {
                var nombre : String = ""  --> no tiene porqué ser optional
                init( nombreCiudad:String) {
                        self.nombre = nombreCiudad
                }
        }

Y tendría razón, así resolvemos también el problema. Pero podríamos complicar el caso un poco más y la solución ya no sería tan sencilla:

        class Ciudad {
                var elAlcalde : Alcalde!    --> se inicializa a nil
                init( nombreAlcalde:String) {
                        self.elAlcalde = Alcalde(nombreAlcalde)
                }
        }

Una variable implicit unwrapped optional es realmente una variable optional (por debajo) pero se puede utilizar como una variable no optional sin necesidad de hacer unwrapping cada vez que se usa. Esta es la diferencia:

        let a : String? = "Hola"
        var a1 = a!   --> es necesario forced unwrapping

        let b : String! = "Adiós"
        var b1 = b  --> se accede normalmente, a pesar de que b es optional

Con las variables implicit unwrapped optional estamos dando permiso al compilador para que haga un unwrap automáticamente cada vez que se utiliza. Es nuestra responsabilidad asegurar que la variable contiene un valor (si no lo tuviera, se elevaría una excepción de runtime).

Es posible también hacer un chequeo del valor de la variable cuando no estemos seguros:

        var a : String! = "hola"
        if a != nil {
                var b = a
        }

Yo creo que las variables implicit unwrapped optional son muy similares a las variables objeto de otros lenguajes, donde es nuestra responsabilidad comprobar previamente si tienen un valor, y si no lo tienen, se eleva una excepción.

CONCLUSIÓN

Apple recomienda utilizar variables optional ? cuando no estemos seguros de que la variable siempre va a contener un valor. Si estamos seguros de que siempre va a contener un valor, usemos implicit unwrapped optional !. Y si se inicializa con un valor y siempre va a tenerlo, entonces usaremos variables no optional. Éste sería un resumen final de todo este capítulo dedicado al novedoso tema de las variables optional en swift.

Si tenéis comentarios, sugerencias, habéis visto errores, por favor, no dudéis en poner un comentario en esta entrada de blog.











viernes, 9 de enero de 2015

Javascript avanzado: Funciones

Este es el primer artículo de una serie que tengo la intención de escribir sobre javascript avanzado. Empezaré por intentar explicar algunos conceptos avanzados de las funciones en javascript.

Muchos desarrolladores, cuando les preguntan ¿sabes javascript? Responden un sí inmediata y contundentemente. Yo era uno de ellos, hasta hace poco.
Muchos pensábamos que javascript se reduce a una serie de tipos básicos, condiciones if, bucles for/while y declaración de funciones. Algunos incluso hemos utilizado callbacks para eventos en páginas html.
También, muchos pensábamos que javascript se utiliza única y exclusivamente en el desarrollo web, dentro las páginas html. Efectivamente, así empezó javascript. Pero hoy en día, esto no es cierto. Javascript es un lenguaje muy avanzado y que se utiliza en muchos otros entornos además del desarrollo web, por ejemplo, en el desarrollo de aplicaciones para móvil.

Javascript es uno de los lenguajes más avanzados y expresivos que existen actualmente (esto puede sorprender a más de uno). Estas son algunas de sus características destacadas:

  • Rendimiento: Javascript utiliza compilación just-in-time, o sea, se compila y se traduce a código máquina justo antes de ejecutarse. Su rendimiento es similar al de aplicaciones escritas en C o C++. Sin embargo, javascript tiene la penalización de la recolección de basura y enlazado dinámico de tipos (las variables pueden cambiar de tipo dinámicamente).
  • Objectos: Javascript tiene características avanzadas de orientación a objetos. Utiliza el modelo de herencia con prototipos (en lugar de clases, tenemos prototipos de objetos). Este modelo es más potente y flexible que el modelo de herencia con clases utilizado en lenguajes como Java. Javascript también soporta, a pesar de ser poco conocidas, las siguientes características: encapsulación, polimorfismo, herencia múltiple y composición.
  • Sintaxis: La sintaxis de javascript es similar a la de lenguajes como C++, Java, C# o PHP. Esta es una de las causas del éxito de javascript.
  • Funciones: En javascript, casi todo son objetos, incluidas las funciones. Por este motivo, podemos usar una función en cualquier sitio en que podemos utilizar una variable (por ejemplo en un parámetro de una llamada a función). Las funciones que reciben como parámetro una función o retornan una función como resultado, se llaman funciones de alto orden.
  • Reutilización: Javascript es portable, es decir, puede ejecutarse en cualquier plataforma.
Comencemos por tanto explicando algunos conceptos, no demasiado avanzados sobre las funciones en javascript, pero que introducen los conceptos más avanzados que se explicarán posteriormente. En posteriores artículos hablaré del ámbito (scope), closures, this, prototype.

Las funciones son datos

En javascript existen los siguientes tipos:

  • PRIMITIVOS
    • Number: desde 5e-308 hasta 1e+308, Infinity, NaN
    • Boolean: true, false
    • String: "....", '....'
    • undefined: cuando una variable se declara, su valor es undefined
    • null: se puede asignar null a una variable
  • OBJECT
    • Object
    • function

Las funciones son también un dato, concretamente, Object.

var f = function(v) {
    return v+1;
}
typeof f;   // "function"


Funciones de callback

function add(f1, f2) {
    return f1() + f2();
}
var one = function() { return 1; }
var two = function() { return 2; }
add(one, two);   // 3

Funciones inmediatas

Las funciones inmediatas se ejecutan inmediatamente:

(function() {
    alert('hello');
})();

Las funciones inmediatas pueden recibir parámetros:

(function(name) {
    alert('hello '+name);
})('world');
Las funciones inmediatas se pueden utilizar para tareas de inicialización, ya que se ejecutan inmediatamente y todas sus variables locales quedan dentro, en privado.

Funciones privadas

function outer(p1) {
    function inner(p2) {
        return 2 * p2;
    }
    return 10 + inner(p1);
}

La función inner() no es accesible desde fuera.

Función que retorna una función

function a() {
    return function() { return 'A'; };
}

var f = a();   // asigna la función interna a la variable f
f();       // invoca la función interna

También se puede invocar la función interna del siguiente modo:

a()();
Se puede reasignar una función. De esta forma, la primera vez que se ejecuta la función puede realizar labores de inicialización y después su función habitual:

function f() {
    // labores iniciales
    .......
    return function() { ...... labores habituales .... };
}
f = f(); 
   
Otra forma más elegante de reasignar una función:

function a() {
    ... código que se ejecuta la primera vez
    a = function() { ... código habitual .... };
}
Y otra forma más, utilizando funciones inmediatas:

var a = (function() {
    function init() {
        .... inicialización
    }
    function work() {
        .... trabajo real
    }
    init();
    return work;
})();









lunes, 17 de noviembre de 2014

Notificaciones push para Android con Corona SDK y Gamethrive


NOTA: Utilizaré el servicio GameThrive, el cuál tiene la ventaja de ser gratuito, ser compatible con notificaciones Android e iOS, y además tiene un plugin gratuito para Corona SDK.


1. Registrarse en Gamethrive (si no estamos registrados previamente).

https://gamethrive.com/

2. Crear un projecto con notificaciones en Google

https://console.developers.google.com/project

Pulsar en "Crear proyecto"

Dar un nombre al proyecto.
El ID del proyecto da igual.
Pulsar el botón "Crear" (puede tardar casi un minuto en crear el proyecto).

En el frame de la izquierda, pulsar en "Descripción general".
Arriba aparece el número del proyecto (#PN). Este dato será necesario para enviar notificaciones a la aplicación.

En el  frame de la izquierda, pulsar en APIs y autenticación. Después pulsar en APIs
Buscar "Google Cloud Messaging for Android" y pulsar en el botón a la derecha de esta línea para activar el servicio.
Una vez activado, aparecerá arriba en la pantalla con los demás servicios activados.

En el frame de la izquierda, pulsar en APIs y autenticación. Después pulsar en Credenciales.
Pulsar el botón "Crear clave nueva".
Pulsar en "Clave de servidor"
No escribir nada en el textbox y pulsar el botón "Crear".
La clave de la API (#KEY) la necesitaremos después.

3. Crear una aplicación en Gamethrive

Entrar en la cuenta de Gamethrive que hemos creado en el apartado 1.

Ir al panel de aplicaciones.

Pulsar el botón "Add a new app"
Dar un nombre a la aplicación (se recomienda poner el mismo nombre que se puso en Google, para evitar confusiones).
Pulsar el botón "Submit".

En el frame izquierdo, pulsar "Application Settings"
Se muestran App ID/Key (#APPID) y REST API Key (#REST) que se utilizarán después.
En la sección "Platforms", pulsar el botón "Configure" en la línea de "Google Play".
Copiar la clave que se generó en Google al final del apartado 2 (#KEY).
Pulsar el botón "Save".
Aparecerá la línea "Google Play" como "Active"

4. Corona build.settings

Dentro de settings, añadir el siguiente elemento, por ejemplo, después del elemento orientation:

plugins =
{
    ["plugin.GameThrivePushNotifications"] =
    {
        publisherId = "com.gamethrive",
    },
    ["plugin.google.play.services"] =
    {
            publisherId = "com.coronalabs",
            supportedPlatforms = { android=true },
    },
},

También dentro de settings, hay que poner la siguiente sección:

        android =
        {
            permissions =
            {
                { name = ".permission.C2D_MESSAGE", protectionLevel = "signature" },
            },
            usesPermissions =
            {
                "android.permission.INTERNET",
                "android.permission.GET_ACCOUNTS",
                "android.permission.RECEIVE_BOOT_COMPLETED",
                "com.google.android.c2dm.permission.RECEIVE",
                ".permission.C2D_MESSAGE",
            },
        },


4. Corona config.lua

Dentro de application, añadir el siguiente elemento, por ejemplo, después del elemento content:

        notification = {
            google = { projectNumber = "#PN" },
        }, 

5. Corona main.lua

Al principio del fichero main.lua:


-- Esta función es llamada cuando el usuario abre una notificación o cuando la aplicación está abierta y activa.
function DidReceiveRemoteNotification(message, additionalData, isActive)
    if (additionalData) then
        if (additionalData.discount) then
            native.showAlert( "Discount!", message, { "OK" } )
            -- 
        elseif(additionalData.bonusCredits) then
            native.showAlert( "Bonus Credits!", message, { "OK" } )
            -- 
        elseif(additionalData.actionSelected) then -- Interactive notification button pressed
            native.showAlert("Button Pressed!", "ButtonID:" .. additionalData.actionSelected, { "OK"} )
        end
    end
end

local GameThrive = require("plugin.GameThrivePushNotifications")
GameThrive.Init("poner aquí #APPID de gamethrive", "poner aquí #PN de google", DidReceiveRemoteNotification)

La función DidReceiveRemoteNotification se ejecutará cuando el usuario abra una notificación.
Casos:
  • El móvil está apagado: cuando se encienda el móvil aparecerá la notificación y al pulsarla, se abrirá la aplicación y se ejecutará esta función.
  • La aplicación está abierta y activa (foreground): se invocará esta función, aunque no aparecerá el icono de la notificación
  • Si la aplicación no está activa: aparecerá el icono de la notificación y cuando el usuario lo pulse, se abrirá la aplicación y se ejecutará esta función.
Parámetros de esta función:
  • message: es el mensaje de la notificación.
  • additionalData: son datos adicionales que podemos añadir a la notificación (ya se envíe desde la web de gamethrive o bien a través del API de gamethrive).
  • isActive: indica (true o false) si la aplicación estaba activa.

6. Icono de notificación

El icono de notificación debe ser un fichero en formato PNG, con fondo transparente y color blanco.
Se debe crear un icono para cada una de las distintas densidades de pantalla y otro conjunto de iconos para Android API v11. En resumen, hay que crear los siguientes iconos:


  • IconNotificationDefault-ldpi.png ---> 18x18 px
  • IconNotificationDefault-ldpi-v11.png ---> 18x18 px
  • IconNotificationDefault-mdpi.png ---> 24x24 px
  • IconNotificationDefault-mdpi-v11.png ---> 24x24 px
  • IconNotificationDefault-hdpi.png ---> 36x36 px
  • IconNotificationDefault-hdpi-v11.png ---> 36x36px
  • IconNotificationDefault-xhdpi.png ---> 48x48 px
  • IconNotificationDefault-xhdpi-v11.png ---> 48x48 px

Hay que tener cuidado de dejar al menos un pixel en ldpi, un pixel en mdpi, un pixel en hdpi y dos pixel en xhdpi, como borde entre la imagen y el icono.




jueves, 28 de agosto de 2014

Estados de una Activity en Android

ESTADOS

Los estados en los que puede estar una Activity en Android son:

  • NON-EXIST
  • STOPPED: no visible
  • PAUSED: visible
  • RUNNING: visible y en foreground
Las transiciones entre estados y las funciones de la Activity invocadas son las siguientes:

  • NON-EXIST --> STOPPED: onCreate()
  • STOPPED --> PAUSED: onStart()
  • PAUSED --> RUNNING: onResume()
  • RUNNING --> PAUSED: onPause() y onSaveInstanceState()
  • PAUSED --> STOPPED: onStop()
  • STOPPED --> NON-EXIST: onDestroy()


EVENTOS QUE CAUSAN CAMBIOS DE ESTADO

Lanzar la Activity

Pasa de estado NON-EXIST a estado RUNNIG, invocándose las siguientes funciones:
  • onCreate()
  • onStart()
  • onResume()

Pulsar Back

Pasa de estado RUNNIG a estado NON-EXIST, invocándose las siguientes funciones:
    • onPause()
    • onSaveInstanceState()
    • onStop()
    • onDestroy()

    Pulsar Home

    Pasa de estado RUNNIG a estado STOP, invocándose las siguientes funciones:
        • onPause()
        • onSaveInstanceState()
        • onStop()

        Abrir la aplicación en recientes después de Home

        Pasa de estado STOP a estado RUNNING, invocándose las siguientes funciones:
            • onStart()
            • onResume()

            Abrir la aplicación en recientes después de Back

            Pasa de estado NON-EXIST a estado RUNNING, invocándose las siguientes funciones:
                • onCreate()
                • onStart()
                • onResume()

                Rotar dispositivo

                Pasa de estado RUNNIG a estado RUNNING, invocándose las siguientes funciones:
                    • onPause()
                    • onSaveInstanceState()
                    • onStop()
                    • onDestroy()
                    • onCreate()
                    • onStart()
                    • onResume()

                    Lanzar otra Activity desde la Activity

                    Pasa de estado RUNNIG a estado STOP, invocándose las siguientes funciones:
                        • onPause()
                        • onSaveInstanceState()
                        • onStop()

                        Pulsar Back en la otra Activity

                        Pasa de estado STOP a estado RUNNING, invocándose las siguientes funciones:
                            • onStart()
                            • onResume()


                            CÓMO VE EL S.O. ANDROID LAS ACTIVITIES

                            Cuando el usuario pulsa sobre una aplicación en su móvil o tablet Android, el sistema operativo no arranca la aplicación, sino que arranca una Activity y la pone en estado RUNNING.
                            Normalmente, las aplicaciones contienen múltiples activities, ¿cuál de ellas se arranca? Esto viene definido en el fichero AndroidManifest.xml. Una activity tendrá definido:

                               

                            dentro de

                            Si desde la Activity inicial se arranca otra Activity, el sistema arrancará la nueva Activity y la pondrá en estado RUNNING (encima de la anterior). Estas dos actividades están en el stack de actividades de Android.
                            Si se pulsa Back, la actividad actual se destruye y vuelve la actividad inicial a estado RUNNING (el mismo efecto se consigue invocando el método finish() ).

                            Si se pulsa Back en la actividad inicial, se destruye la Activity inicial y volveremos donde estábamos antes de arrancar la aplicación.

                            El responsable de realizar todas estas tareas es el ActivityManager de Android, el cual mantiene un stack de actividades compartido entre todas las aplicaciones.