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.







No hay comentarios:

Publicar un comentario