Programación en Swift y SwiftUI para iOS Developers

Liquid Glass en botones en SwiftUI

En el vertiginoso mundo del diseño de interfaces móviles, las tendencias son pendulares. Hemos pasado del realismo extremo al diseño plano, y ahora, nos encontramos en un punto medio fascinante: la profundidad material. Para un iOS developer, mantenerse al día no es solo una opción, es una necesidad. Hoy, la tendencia que define las aplicaciones más premium de la App Store es el Liquid Glass (Cristal Líquido).

A diferencia del “Glassmorphism” estático de hace unos años, el efecto Liquid Glass añade una dimensión orgánica: sugiere viscosidad, refracción de luz realista y una interacción táctil que imita la física de los fluidos.

En este tutorial de programación Swift, dejaremos de lado las teorías abstractas y abriremos Xcode para construir, paso a paso, un componente de botón con efecto cristalino que funcione nativamente en SwiftUI. Optimizaremos este componente para que brille en iOS, se adapte al ratón en macOS y sea eficiente en la muñeca con watchOS.


1. La Teoría Visual: ¿Qué hace que el cristal parezca “Líquido”?

Antes de escribir código, debemos deconstruir la física que vamos a simular. Si simplemente bajas la opacidad de un botón, no obtienes cristal; obtienes un fantasma. Para lograr el efecto liquid glass a un botón, necesitamos combinar cuatro factores en nuestra vista:

  1. Desenfoque de Fondo (Background Blur): El objeto debe distorsionar lo que hay detrás.
  2. Saturación Vibrante: El cristal tiende a amplificar los colores que refracta.
  3. Bordes Especulares (Specular Highlight): La luz se acumula en los bordes. Un borde blanco semitransparente que varía en intensidad es crucial para dar volumen 3D.
  4. Sombra Proyectada y Luz Ambiental: El cristal flota sobre la interfaz, por lo que necesita separarse del fondo mediante sombras suaves.

Gracias a la potencia de SwiftUI y las últimas actualizaciones de Swift, podemos lograr esto con un rendimiento nativo de 60/120 FPS sin recurrir a imágenes pesadas.


2. Preparando el Proyecto en Xcode

Abre Xcode y crea un nuevo proyecto. Asegúrate de seleccionar:

  • Interface: SwiftUI
  • Language: Swift
  • Storage: None (no necesitamos Core Data para esto)

Para este tutorial, utilizaremos las APIs de materiales (Material) introducidas recientemente, que son mucho más potentes que las antiguas UIVisualEffectView de UIKit.


3. Paso 1: El Escenario (La importancia del Fondo)

El error número uno al intentar crear un efecto liquid glass a un botón es probarlo sobre un fondo blanco o negro sólido. El cristal es invisible si no hay nada detrás que refractar.

Vamos a crear una vista de fondo rica y colorida. Usaremos círculos con desenfoque extremo para simular luces ambientales.

import SwiftUI

struct AmbientBackgroundView: View {
    @State private var animateGradient = false
    
    var body: some View {
        ZStack {
            // Fondo base oscuro para resaltar los brillos
            Color(red: 0.1, green: 0.1, blue: 0.2)
                .ignoresSafeArea()
            
            // Orbes de luz animados
            ZStack {
                Circle()
                    .fill(Color.blue)
                    .frame(width: 300, height: 300)
                    .blur(radius: 60)
                    .offset(x: animateGradient ? -100 : 100, y: -100)
                
                Circle()
                    .fill(Color.purple)
                    .frame(width: 300, height: 300)
                    .blur(radius: 60)
                    .offset(x: animateGradient ? 100 : -100, y: 100)
                
                Circle()
                    .fill(Color.cyan)
                    .frame(width: 200, height: 200)
                    .blur(radius: 50)
                    .offset(y: animateGradient ? 50 : -50)
            }
            .animation(.easeInOut(duration: 5.0).repeatForever(autoreverses: true), value: animateGradient)
        }
        .onAppear {
            animateGradient.toggle()
        }
    }
}

Nota para el ios developer: El uso de modificadores .blur() consume GPU. En una app real, si el fondo es estático, considera renderizarlo como una imagen, pero para interfaces dinámicas, SwiftUI lo maneja eficientemente.


4. Paso 2: Construyendo el Modificador “Liquid Glass”

En la programación Swift moderna, la reutilización es clave. En lugar de crear un botón rígido, crearemos un ViewModifier. Esto nos permitirá aplicar el efecto de cristal a cualquier cosa: un botón, una tarjeta o un panel de navegación.

Aquí es donde ocurre la magia técnica.

El Código del Modificador

struct LiquidGlassModifier: ViewModifier {
    var cornerRadius: CGFloat = 20
    
    func body(content: Content) -> some View {
        content
            .padding()
            // CAPA 1: El Material
            // .ultraThinMaterial es el estándar para cristal esmerilado
            .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: cornerRadius, style: .continuous))
            
            // CAPA 2: Tinte Sutil
            // Añadimos un poco de blanco para que no sea transparente total
            .background(
                RoundedRectangle(cornerRadius: cornerRadius, style: .continuous)
                    .fill(Color.white.opacity(0.1))
            )
            
            // CAPA 3: El Borde de Luz (Reflexión)
            // Esto da el efecto 3D. La luz viene de arriba a la izquierda.
            .overlay(
                RoundedRectangle(cornerRadius: cornerRadius, style: .continuous)
                    .stroke(
                        LinearGradient(
                            gradient: Gradient(stops: [
                                .init(color: .white.opacity(0.6), location: 0.0), // Brillo fuerte arriba
                                .init(color: .white.opacity(0.2), location: 0.3),
                                .init(color: .clear, location: 0.5),              // Invisible en el centro
                                .init(color: .white.opacity(0.3), location: 1.0)  // Contraluz suave abajo
                            ]),
                            startPoint: .topLeading,
                            endPoint: .bottomTrailing
                        ),
                        lineWidth: 1.5
                    )
            )
            // CAPA 4: Sombra de elevación
            .shadow(color: Color.black.opacity(0.15), radius: 10, x: 0, y: 10)
    }
}

// Extensión para uso fácil
extension View {
    func liquidGlassStyle(cornerRadius: CGFloat = 20) -> some View {
        self.modifier(LiquidGlassModifier(cornerRadius: cornerRadius))
    }
}

Análisis Técnico

  1. style: .continuous: Observa que usamos esto en el RoundedRectangle. Apple usa “squircles” (superelipses), no arcos perfectos. Para que tu app se sienta nativa, siempre usa .continuous.
  2. El Gradiente del Borde: La clave del realismo. Un borde blanco sólido (Color.white) se ve falso y plano. Un gradiente que va de opaco a transparente simula cómo la luz golpea una superficie curva.

5. Paso 3: Interactividad y ButtonStyle

Un botón bonito que no responde bien al tacto es inútil. En SwiftUI, separamos la lógica de la apariencia usando ButtonStyle.

Queremos que el botón se sienta “líquido” o gomoso al presionarlo. Usaremos una animación de resorte (spring) y un cambio de escala.

struct LiquidButtonStyle: ButtonStyle {
    func makeBody(configuration: Configuration) -> some View {
        configuration.label
            .font(.system(size: 17, weight: .semibold, design: .rounded)) // Fuente redondeada para acompañar el estilo
            .foregroundColor(.white)
            .padding(.horizontal, 20)
            .padding(.vertical, 10)
            // Aplicamos nuestro modificador visual
            .liquidGlassStyle(cornerRadius: 30)
            // Efectos de interacción
            .scaleEffect(configuration.isPressed ? 0.95 : 1.0)
            .opacity(configuration.isPressed ? 0.8 : 1.0)
            // Animación fluida
            .animation(.spring(response: 0.3, dampingFraction: 0.6), value: configuration.isPressed)
    }
}

6. Paso 4: Implementación Multiplataforma

El ecosistema Apple es vasto. Un buen ios developer sabe que el código debe ser flexible. ¿Cómo adaptamos este efecto para Mac y Watch?

Adaptación para macOS (Hover Effect)

En iOS tocamos con el dedo, pero en macOS usamos un cursor. El botón debe reaccionar antes de ser clicado (Hover).

Podemos modificar nuestro estilo para detectar el ratón:

struct LiquidButtonMacOSAdapted: View {
    @State private var isHovered = false
    var title: String
    var action: () -> Void

    var body: some View {
        Button(action: action) {
            Text(title)
        }
        .buttonStyle(PlainButtonStyle()) // Reseteamos estilo nativo de Mac
        .font(.body.bold())
        .foregroundColor(.white)
        .padding()
        // Fondo Condicional
        .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 16))
        .overlay(
            RoundedRectangle(cornerRadius: 16)
                .stroke(isHovered ? Color.white.opacity(0.8) : Color.white.opacity(0.3), lineWidth: 1)
        )
        .scaleEffect(isHovered ? 1.02 : 1.0)
        .onHover { hovering in
            withAnimation(.easeInOut(duration: 0.2)) {
                isHovered = hovering
            }
        }
    }
}

Adaptación para watchOS (Rendimiento)

El Apple Watch utiliza pantallas OLED y tiene baterías pequeñas. El ultraThinMaterial sobre animaciones complejas puede drenar la batería.

  • Consejo: En watchOS, reduce el radio de desenfoque de los elementos de fondo o usa un color sólido semitransparente (Color.white.opacity(0.15)) en lugar del material costoso si detectas caídas de frames.

7. Paso 5: Uniendo todo en la Vista Principal

Ahora, vamos a ver el resultado final en nuestra ContentView.

struct ContentView: View {
    var body: some View {
        ZStack {
            // 1. Fondo Ambiental
            AmbientBackgroundView()
            
            VStack(spacing: 40) {
                Text("SwiftUI Design")
                    .font(.largeTitle.weight(.heavy))
                    .foregroundColor(.white)
                    .shadow(radius: 10)
                
                // 2. Nuestro Botón Liquid Glass
                Button(action: {
                    print("Botón presionado")
                }) {
                    HStack {
                        Image(systemName: "drop.fill")
                        Text("Iniciar Experiencia")
                    }
                }
                .buttonStyle(LiquidButtonStyle())
                
                // Variación: Tarjeta de información
                VStack(alignment: .leading) {
                    Text("Tarjeta de Cristal")
                        .font(.headline)
                        .foregroundStyle(.white)
                    Text("Este mismo efecto se puede aplicar a paneles completos.")
                        .font(.caption)
                        .foregroundStyle(.white.opacity(0.8))
                }
                .liquidGlassStyle(cornerRadius: 15)
                .frame(width: 250)
            }
        }
    }
}

8. Consideraciones Avanzadas para el Desarrollador Senior

Para que este artículo aporte valor real a tu carrera y conocimientos en xcode, profundicemos en dos aspectos críticos: Accesibilidad y Rendimiento.

Accesibilidad (A11y)

El efecto cristal tiene un riesgo inherente: el bajo contraste.

  • Legibilidad: Asegúrate de que el texto sobre el botón tenga suficiente peso (Bold o Semibold).
  • Sombra de Texto: Una técnica sutil pero vital es añadir .shadow(color: .black.opacity(0.3), radius: 1, x: 0, y: 1) al texto dentro del botón. Esto garantiza que, incluso si el fondo cambia a un color claro (por la transparencia), el texto blanco siga siendo legible.
  • Reducción de Transparencia: iOS permite a los usuarios reducir transparencias en Ajustes. Debes respetar esto usando la variable de entorno @Environment(\.accessibilityReduceTransparency).
@Environment(\.accessibilityReduceTransparency) var reduceTransparency

var backgroundShape: some View {
    if reduceTransparency {
        return Color.black // Fondo sólido para accesibilidad
    } else {
        return Material.ultraThin // Efecto cristal normal
    }
}

Optimización de Renderizado

El modificador .background(.ultraThinMaterial) provoca un paso de renderizado adicional fuera de la pantalla.

  • No abuses de este efecto en celdas de una List o LazyVStack con cientos de elementos. El scroll podría sufrir “stuttering” (tirones) en dispositivos más antiguos.
  • Úsalo para elementos estáticos o de jerarquía superior (Botones de llamada a la acción, Barras de navegación, Modales).

Conclusión

Dominar el efecto liquid glass a un botón en SwiftUI es más que un truco cosmético; es una demostración de control sobre la jerarquía de vistas, los modos de fusión y la física de la interfaz.

Como hemos visto, Swift y Xcode nos proporcionan todas las herramientas nativas necesarias sin depender de librerías de terceros. La clave del éxito reside en la sutileza: un buen borde gradiente, una sombra bien proyectada y, sobre todo, un fondo interesante que justifique el uso del cristal.

Al implementar estos patrones de diseño, no solo mejoras la estética de tu aplicación, sino que demuestras un nivel de cuidado y detalle que los usuarios de Apple esperan y valoran.

Si tienes cualquier duda sobre este artículo, contacta conmigo y estaré encantado de ayudarte 🙂. Puedes contactar conmigo en mi perfil de X o en mi perfil de Instagram

Leave a Reply

Your email address will not be published. Required fields are marked *

Previous Article

Cómo mostrar un popover en SwiftUI

Next Article

Cómo crear un editor de texto enriquecido en SwiftUI

Related Posts