Liquid Glass, medido
Dos números, 120 tirones por minuto y 5, lo explican todo sobre lo que cuesta de verdad Liquid Glass y dónde está ese coste.
El número que me frenó fue 120. Tirones por minuto, con Instruments abierto, diez tarjetas de cristal en pantalla y la animación de brillo en marcha. Yo había tratado la respuesta a la inclinación como algo prácticamente gratis: se veía bien, seguía la luz como lo haría un cristal real, parecía el detalle adecuado. Luego lo medí.
Cambiar a un brillo estático con quince tarjetas bajó el número a cinco. Más tarjetas, sin movimiento: una lista más llena y 115 tirones menos por minuto. El cristal se quedó. La animación, no.
Esa diferencia es el tema de este artículo.
Lo que descarté primero
La hipótesis obvia era el número de tarjetas. Más superficies en pantalla, más trabajo: esa lógica vale para muchos problemas de renderizado, y la perseguí más tiempo del que debía. Lo que probé: bajar de 60 Hz a 30. Pasar el brillo del renderizado de SwiftUI a un CALayer. Activar la animación según el movimiento, de modo que solo corriera cuando el acelerómetro indicara que el dispositivo estaba quieto. Limitar la tarjeta más alta para que no pudiera crecer más allá de una altura fija. Nada de eso movió los números lo suficiente como para importar.
La solución que funcionó fue también la más sencilla: motionEnabled = false. Brillo estático, sin respuesta a la inclinación, sin muestreo en cada fotograma.
Lo que está haciendo realmente el sistema
.glassEffect funciona muestreando lo que tiene detrás. Eso es lo que hace que parezca cristal y no un rectángulo esmerilado: lee lo que hay detrás y aplica desenfoque, tinte y el reflejo especular sobre esa muestra. La muestra es en vivo. Cuando algo se mueve por encima de una superficie de cristal, el sistema tiene que recomponer el fondo de cada superficie de cristal en pantalla, no solo de la que está más cerca del movimiento.
Diez tarjetas. Sesenta fotogramas por segundo. Una animación de brillo corriendo por encima. Sesenta recomposiciones por superficie por segundo, multiplicadas por cada tarjeta de cristal de la jerarquía de vistas. 120 tirones por minuto.
Quince tarjetas con brillo estático: el fondo se muestrea una vez y se mantiene hasta que algo cambia. Cinco tirones por minuto.
El borde especular sigue existiendo en la versión publicada. Está iluminado desde un ángulo fijo y nunca se mueve. Lo que tuvo que irse fue la respuesta a la inclinación, la parte que seguía la orientación del dispositivo y hacía que el reflejo se desplazara al mover el teléfono. La tarjeta se sigue leyendo como cristal. Simplemente no responde a la gravedad.
El área es lo que de verdad determina el coste
Una vez que tuve claro el modelo de recomposición, vino otra conclusión: el coste depende del área del fondo, no del número de superficies de cristal.
Treinta y cinco tarjetas estrechas cuestan menos que diez altas. Lo que el sistema compone es el área total de pantalla cubierta por cristal, y una tarjeta pequeña aporta proporcionalmente menos que una grande, sin importar cuántas haya. Una tarjeta de estadísticas en lo alto de una lista (pequeña, de altura fija) es barata. Una tarjeta que ocupa casi todo el ancho de la pantalla y crece con su contenido es cara.
Esto cambia la pregunta de diseño. No es «¿cuántas superficies de cristal me puedo permitir en esta pantalla?». Es «¿qué parte de la pantalla es cristal en cada posición de desplazamiento?».
El problema de la tarjeta alta
Algunas tarjetas tienen que ser altas. La tarjeta de citas, por ejemplo, podía crecer sin límite a medida que se añadían elementos. Sin restricciones, el área de su fondo crece con ella, y también el coste por fotograma.
La solución es limitar la altura. Dale a la tarjeta una altura máxima y deja que el contenido se desplace por dentro. El sistema de cristal ve una superficie de tamaño constante, como un mosaico, sin importar cuántas filas haya dentro. La tarjeta es asequible porque el área de su fondo está acotada.
Es una de esas restricciones que también mejoran el diseño, al margen del rendimiento. Una tarjeta que crece sin límite tiende a empujar todo lo demás fuera de la pantalla de todos modos.
Dos detalles de implementación que conviene conocer
GlassEffectContainer agrupa las superficies de cristal hermanas en una sola pasada de composición. Si tienes varios elementos de cristal al mismo nivel de la jerarquía (una fila de tarjetas de estadísticas, una cuadrícula), vale la pena envolverlos en el contenedor. Sin él, cada superficie se compone por separado.
El otro detalle: .clipShape tiene que ir antes que .background en la cadena de modificadores.
// Wrong — the background bleeds past the corners
.background(material)
.clipShape(RoundedRectangle(cornerRadius: 16))
// Right
.clipShape(RoundedRectangle(cornerRadius: 16))
.background(material)
El material de cristal no recorta a sus hijos. Una fila que pinta su propio fondo dibujará esquinas rectas que sobresalen de la forma redondeada de la tarjeta si el recorte va después. Lo descubrí por las malas en una lista con filas internas.
Lo que esto le costó al diseño
La respuesta a la inclinación ya no está. Era el comportamiento más claramente propio del cristal (el reflejo moviéndose al mover el teléfono), y es lo que sacrificas cuando el perfilador dice 120.
Lo que se quedó: el cuerpo esmerilado, el borde especular, el degradado angular del reflejo del brillo. La tarjeta se lee como cristal. No se comporta como cristal en el sentido físico.
La otra restricción que impusieron las mediciones: las tarjetas de estadísticas siguen siendo de cristal transparente, nunca tintado. Un tinte que funciona en una tarjeta con etiquetas blancas vuelve invisible el texto en el color principal. La codificación por colores corresponde a los datos (el minigráfico, el indicador), no a la superficie.
El archivo es la documentación
LiquidGlassCard.swift está copiado tal cual en The Smart Dentist, Billing y Coach. A propósito. Las reglas de rendimiento viajan con el archivo en lugar de vivir en una nota en alguna parte. El contexto del 120 a 5 está en el mismo archivo fuente que el código, junto a los parámetros y los comentarios, para que quien lo abra después no tenga que redescubrir nada de esto.
Los números son el documento de diseño. Todo lo demás es un comentario.