La identidad estructural de SwiftUI

Andres Felipe Ocampo
6 min readJun 16, 2022

--

SwiftUI usa la estructura de su jerarquía de vistas para generar identidades implícitas para sus vistas para que no tenga que hacerlo. Ahora, voy a explicar lo que quiero decir con eso. Digamos que tenemos dos perros similares pero no sabemos sus nombres, pero aún necesitamos identificar a cada uno.
Bueno, supongamos que estos son muy buenos perros y son capaces de quedarse muy quietos.

Si podemos garantizar que no se mueven, podríamos identificarlos basándonos en dónde están sentados, como “El perro de la izquierda” o “El perro de la derecha”. Usamos la disposición relativa de nuestros sujetos para distinguirlos unos de otros, eso es identidad estructural.

SwiftUI aprovecha la identidad estructural en toda su API, y un ejemplo clásico es cuando usa declaraciones if y otra lógica condicional dentro de tu código View.

La estructura de la declaración condicional nos da una forma clara de identificar cada punto de vista. La primera vista solo muestra cuando la condición es verdadera, mientras que la segunda vista solo muestra cuando la condición es falsa.

Eso significa que siempre podemos saber qué vista es cuál, incluso si tienen un aspecto similar. Sin embargo, esto solo funciona si SwiftUI puede garantizar estáticamente que estas vistas permanezcan donde están y nunca cambien de lugar. SwiftUI logra esto observando la estructura de tipos de su jerarquía de vistas.

Cuando SwiftUI mira tus vistas, ve tus tipos genéricos; en este caso, nuestra declaración if se tradujo a una vista _ConditionalContent, que es genérica sobre tu contenido verdadero y falso. Esta traducción está impulsada por un ViewBuilder, que es un tipo de generador de resultados en Swift.

El protocolo View envuelve implícitamente su propiedad de “body” en un ViewBuilder, que construye una sola vista genérica a partir de las declaraciones lógicas en nuestra propiedad. El tipo de retorno some View de nuestra propiedad body es un marcador de posición que representa este tipo compuesto estático, ocultándolo para que no abarrote nuestro código.
Con este tipo genérico, SwiftUI puede garantizar que la vista b siempre será DirectorioAdopcionView, mientras que la vista Falsa siempre será ListaPerrosView, lo que les permite asignarles una identidad implícita y estable en segundo plano.
De hecho, esta es la clave para comprender la aplicación del articulo anterior.

Con el código en la parte superior, tenemos una instrucción if que define diferentes vistas para cada rama condicional. Esto hará que las vistas entren y salgan porque SwiftUI entiende que cada rama de la instrucción if representa una vista diferente con una identidad distinta. Alternativamente, podríamos tener un solo DogView que cambie su diseño y color. Cuando cambia a un estado diferente, la vista se deslizará suavemente a su siguiente posición.

Eso es porque estamos modificando una sola vista con una identidad consistente.

Ambas estrategias pueden funcionar, pero SwiftUI generalmente recomienda un segundo enfoque.

De forma predeterminada, intentar preservar la identidad y proporcionar transiciones más fluidas. Esto también ayuda a preservar el tiempo de vida y el estado de su vista, de lo que hablaremos con más detalle más adelante.

Ahora que entendemos la identidad estructural, debemos hablar sobre tu archiconocido, AnyView. Para comprender el impacto del uso de AnyView, veamos el efecto que tiene en la estructura de tus vistas.
Anteriormente escribimos esta declaración if para cambiar entre DirectorioAdopcionView y ListaPerrosView. Cuando SwiftUI mira este código, ve la estructura de tipo genérico, esto está claro.

Ahora veamos un ejemplo diferente, uno que usa AnyView extensivamente.
Esta es una función de ayuda que he escrito para obtener una vista que represente quien es cada perro. Cada rama condicional de la función devuelve un tipo diferente de vista, por lo que las envolví todas en AnyViews porque Swift requiere un solo tipo de devolución para toda la función.

Desafortunadamente, esto también significa que SwiftUI no puede ver la estructura condicional de mi código. En cambio, solo ve un AnyView como un tipo de retorno de la función. Esto se debe a que AnyView es lo que se denomina un “tipo contenedor de borrado de tipo / type-erasing wrapper type”: oculta el tipo de vista que está ajustando de su firma genérica.

Pero quizás lo más importante es que este código también es realmente difícil de leer para nosotros, simples mortales.
Veamos si podemos simplificar este código y también hacer más visible su estructura para SwiftUI.

Como vimos anteriormente, el código SwiftUI View normal puede usar declaraciones if que devuelven diferentes tipos de vistas. Pero si solo intentamos eliminar las declaraciones return y AnyViews de nuestro código, aparecerán algunos errores y advertencias. Esto se debe a que SwiftUI requiere un solo tipo de retorno de nuestra función auxiliar.

Entonces, ¿cómo podemos evitar estos errores? Recuerda que la propiedad del body de una View es especial, porque el protocolo View la envuelve implícitamente en un ViewBuilder. Esto traduce la lógica de la propiedad en una única estructura de vista genérica.
Ahora, Swift no infiere que las funciones auxiliares sean constructores de vistas de forma predeterminada, pero podemos optar por eso aplicando manualmente el atributo ViewBuilder nosotros mismos.

Y eso nos permite eliminar las declaraciones de return y los envoltorios AnyView sin advertencias ni errores.

Mother of dragonss, ¡nuestro código se ve bastante bien ahora! Nos hemos deshecho de todos los AnyViews, por lo que es más fácil de leer que antes.
Y si observamos la firma de tipo del resultado, ahora replica exactamente la lógica condicional de nuestra función con un árbol de contenido condicional, lo que brinda a SwiftUI una perspectiva mucho más rica de la vista y las identidades de sus componentes.

Pero hay una pequeña mejora más que podemos hacer implementando en lugar de usar un if un switch case: así que es mas interesante tener encuenta un enumerado de perritos, huuuyeah!.

Con esto es más que suficiente para hoy, ya nos veremos con la continuación del entendimiento más profundo de SwiftUI.

--

--

Andres Felipe Ocampo
Andres Felipe Ocampo

Written by Andres Felipe Ocampo

Digital Manager and Sr Lead iOS Engineer

No responses yet