Lenguaje visual de Autolayout en iOS

Como comenté, voy a dedicar un par de artículos al funcionamiento de Autolayout en iOS. En el primero hice una pequeña introducción sobre este sistema, hablando sobre cómo relacionar las vistas que contienen la interfaz de un determinado storyboard.

En este caso, voy a hablaros sobre el lenguaje visual que puede utilizarse para implementar UI dinámicas directamente desde el código. Para ello voy a basarme en la documentación oficial sobre “visual format” ofrecida por Apple.

La organización de la UI mediante lenguaje visual se basa en la utilización de cadenas de texto para presentar la configuración deseada. Un ejemplo de estas cadenas podría ser la siguiente:

V:|-[button]-[textField(40)]-10-[searchBar(>=20)]-|

Esta cadena indicaría que, a lo largo del eje vertical (V) se disponen 3 vistas: un botón, un campo de texto y una barra de búsqueda. Estas vistas vienen representadas por su nombre entre corchetes.

Estas vistas quedan distribuidas de manera que el botón tiene una separación de 8 px (tamaño por defecto) del borde superior de la vista que lo contiene y otra separación de 8 px al campo de texto.

Por su parte el campo de texto tiene un alto de 40 px y una separación de 10 px a la barra de búsqueda, la cuál tiene un alto de mínimo 20 px, y se encuentra separada 8 px del borde inferior de la supervista.

Recordemos que esto es a lo largo del eje vertical, si en vez de V escribimos H, esto sería respecto a los bordes izquierdo y derecho de la vista contenedora y el tamaño junto a la barra de búsqueda indicaría el ancho y no el alto.

¿Demasiado información? Veamos otros ejemplos más simplificados:

Separación entre vistas

[button]-[textField]

1

Con un guión entre las vistas indicamos una separación estándar de 8 px. Si en cambio especificamos una cantidad determinada de px entre guiones, esa será la separación.

Separación entre vista y su vista contenedora

|-50-[box]-50-|

3

Si en vez de poner una vista en particular ponemos una barra vertical “|” hacemos referencia a la vista contenedora. Por tanto en este caso especificamos un margen de 50 px respecto a los bordes de la vista que contiene a “box”.

Concatenación de vistas

[maroonView][blueView]

5

Si no ponemos guión entre vistas, estas quedarán juntas (equivalente a margen de 0 px).

Atributos

[button(>=50)]

2

Junto al nombre de la vista y entre paréntesis se pueden definir atributos de la vista tales como el tamaño. En este caso se define un ancho o alto (dependiendo del eje en que trabajemos) de mínimo 50 px, es decir, tendrá 50 px o más. Se pueden definir más de un atributo, por ejemplo:

[flexibleButton(>=70,<=100)]

8

Atributos relativos

[button1(==button2)]

7

También se puede indicar que se quiere que una vista tenga el mismo ancho o alto que otra vista, como es el caso de estos dos botones.

Prioridad

[button(100@20)]

6

Junto al valor de su atributo se puede indicar con el símbolo “@“ la prioridad deseada para esa propiedad. Muy útil por ejemplo cuando se quieren distribuir vistas y una de ellas tiene más ancho o alto que el resto.

Hemos visto algunos ejemplo, pero… ¿qué se hace con esta cadena de texto tan extraña? Lo utilizamos en un fragmento de código como el siguiente:


[button setTranslatesAutoresizingMaskIntoConstraints:NO];
    [textField setTranslatesAutoresizingMaskIntoConstraints:NO];

	NSString *layoutFormat = @"H:|-[button]-[textField]-|";
    
    NSDictionary *layoutViews = NSDictionaryOfVariableBindings(button,textField);
    
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:layoutFormat
                                                                   options:NSLayoutFormatAlignAllCenterY
                                                                   metrics:nil
                                                                     views:layoutViews]];

En primer lugar forzamos a las vistas que participarán en la cadena de formato a que no conviertan sus propiedades de reescalado a constraints ya que provocarán conflictos con nuestra especificación.

En segundo lugar elaboramos la cadena de formato. Debemos indicar el nombre de las vistas y en el caso de que se trate una vista propiedad de la clase, no debemos utilizar “self.button” si no la simplificación “_button”.

A continuación se crea un diccionario con las vistas con ayuda de la función “NSDictionaryOfVariableBindings”, que elabora el diccionario estableciendo la clave de los valores (vistas) con el propio nombre de cada una de ellas. Como he comentado, debemos utilizar la nomenclatura de barra baja para las propiedades.

Finalmente utilizamos el método “addConstraints” y “contraintsWithVisualFormat:Options…” para definir las contraints de nuestro layout. Le pasamos como parámetros la cadena de texto que hemos formado, una o varias opciones como por ejemplo “NSLayoutFormatAlignAllCenterY” que alinea las vistas respecto a su eje Y, una cadena de constantes o metrics (se puede dejar sin incluir si ya las hemos incorporado en la cadena de texto) y finalmente las vistas participantes con el diccionario formado anteriormente.

Con esto, hemos conseguido alinear un botón y un campo de texto a lo largo del eje horizontal con una separación estándar entre ellas y la vista contenedora sin hacer uso de ningún storyboard.

Happy coding!