Programación en Swift y SwiftUI para iOS Developers

@Binding vs @Bindable en SwiftUI

En el ecosistema de la programación Swift, la gestión del estado ha sido siempre el corazón palpitante de nuestras aplicaciones. Con la llegada de SwiftUI, pasamos de sincronizar manualmente la UI con los datos a un paradigma declarativo donde la vista es una función directa del estado.

Para cualquier iOS developer que haya empezado con SwiftUI antes de 2023, @Binding era el pan de cada día. Sin embargo, con el lanzamiento de iOS 17, Swift 5.9 y la revolución del framework Observation, apareció un nuevo jugador en Xcode: @Bindable.

La confusión es común: ¿Son lo mismo? ¿Uno reemplaza al otro? ¿Cuándo debo usar cuál? En este tutorial desglosaremos la anatomía de @Bindable y @Binding en SwiftUI, explorando sus semejanzas, diferencias y cómo utilizarlos para crear aplicaciones robustas en iOS, macOS y watchOS.


Parte 1: El Veterano Indispensable: ¿Qué es @Binding?

Para entender el presente, debemos entender los cimientos. @Binding ha estado con nosotros desde la primera versión de SwiftUI.

Definición Técnica

@Binding es un property wrapper (envoltorio de propiedad) que crea una conexión bidireccional (two-way connection) de lectura y escritura entre una vista y una fuente de verdad (source of truth) que reside en otra parte.

Imagina que @Binding es un “cable telefónico”. La vista que tiene el @Binding no posee el dato; no lo almacena en su memoria. Simplemente, tiene una línea directa para leer el valor original y enviar cambios de vuelta al propietario de ese dato.

¿Cuándo se usa @Binding?

El caso de uso clásico es la relación Padre-Hijo.

  1. La Vista Padre posee el estado (usando @State).
  2. La Vista Hija necesita mostrar ese estado Y modificarlo (por ejemplo, un Toggle, un TextField o un Slider).

Ejemplo Práctico en Xcode

Imaginemos una configuración simple de “Modo Avión” en una app de iOS.

import SwiftUI

// VISTA HIJA (Componente reutilizable)
struct AirplaneModeSwitch: View {
    // @Binding indica: "No soy dueño de este booleano, pero puedo cambiarlo"
    @Binding var isOn: Bool

    var body: some View {
        Toggle("Modo Avión", isOn: $isOn) // El signo $ proyecta el Binding
            .padding()
            .background(isOn ? Color.orange.opacity(0.1) : Color.clear)
            .cornerRadius(10)
    }
}

// VISTA PADRE (Dueña de la verdad)
struct SettingsView: View {
    // @State indica: "Yo soy el dueño de este dato"
    @State private var isAirplaneModeEnabled = false

    var body: some View {
        VStack {
            Text("Ajustes de Red")
                .font(.headline)
            
            // Pasamos la referencia con $ para crear el Binding
            AirplaneModeSwitch(isOn: $isAirplaneModeEnabled)
            
            Text(isAirplaneModeEnabled ? "Radio apagada" : "Buscando señal...")
                .font(.caption)
                .foregroundColor(.secondary)
        }
        .padding()
    }
}

Claves para el iOS Developer:

  • Sintaxis: Se pasa con el prefijo $ desde el padre.
  • Tipos de Valor: Funciona excelentemente con tipos de valor simples (Bool, String, Int) y Structs.
  • Limitación: Si empiezas a pasar un @Binding a través de 5 niveles de jerarquía de vistas, tu código empezará a oler mal (Drilling props).

Parte 2: La Revolución de la Observación (Contexto Necesario)

Antes de hablar de @Bindable, debemos hablar del cambio sísmico que ocurrió en la programación Swift en la WWDC 2023.

Apple introdujo el framework Observation. Antes usábamos ObservableObject y @Published (Combine). Ahora, simplemente usamos la macro @Observable.

  • El Viejo Mundo (ObservableObject): Para observar un objeto, la vista necesitaba @ObservedObject o @StateObject.
  • El Nuevo Mundo (@Observable): Las vistas de SwiftUI detectan automáticamente cuándo una propiedad de una clase marcada con @Observable es leída en el body, y se redibujan cuando cambia. Ya no necesitamos @ObservedObject. Pasamos las clases como parámetros normales (let model: User).

Pero aquí surgió un problema: Si paso un objeto como un simple parámetro let, ¿cómo creo Bindings a sus propiedades para mis controles de UI?

Aquí es donde entra @Bindable.


Parte 3: El Nuevo Retador: ¿Qué es @Bindable?

@Bindable es un property wrapper diseñado específicamente para trabajar con tipos de referencia (clases) que utilizan la nueva macro @Observable.

El Problema que Resuelve

En el nuevo sistema Observation, a menudo tienes un objeto modelo:

@Observable
class UserProfile {
    var name: String = ""
    var age: Int = 18
}

Si pasas este objeto a una vista: let profile: UserProfile. No puedes usar $profile.name para enlazarlo a un TextField. ¿Por qué? Porque profile es una constante (let) y no tiene un proyector de binding nativo como lo tenía @State o @ObservedObject.

La Solución con @Bindable

@Bindable toma ese objeto observable y crea los bindings necesarios para que los controles de UI (TextField, Toggle, Picker) puedan escribir en las propiedades del objeto.

Ejemplo Práctico en Xcode

Vamos a crear un formulario de edición de perfil.

import SwiftUI
import Observation

// MODELO DE DATOS (Macro @Observable)
@Observable
class UserSettings {
    var username: String = "DevSwift2024"
    var isPrivateProfile: Bool = true
}

// VISTA DE EDICIÓN
struct ProfileEditor: View {
    // Opción A: Declararlo en los parámetros si la vista DEBE tener bindings
    // @Bindable var settings: UserSettings 
    
    // Opción B (Más común): Recibir el objeto normal y hacerlo bindable dentro
    var settings: UserSettings

    var body: some View {
        // Creamos el ámbito Bindable aquí
        @Bindable var bindableSettings = settings

        Form {
            Section("Información") {
                // Ahora podemos usar $bindableSettings
                TextField("Nombre de usuario", text: $bindableSettings.username)
            }
            
            Section("Privacidad") {
                Toggle("Perfil Privado", isOn: $bindableSettings.isPrivateProfile)
            }
        }
    }
}

// VISTA PRINCIPAL
struct MainProfileView: View {
    @State private var settings = UserSettings()

    var body: some View {
        ProfileEditor(settings: settings)
    }
}

Análisis Profundo

Fíjate en la magia dentro de ProfileEditor. Recibimos var settings: UserSettings. Es una referencia simple. Pero dentro del body, usamos: @Bindable var bindableSettings = settings.

Esta línea “desbloquea” la capacidad de usar el signo $ para modificar las propiedades de la clase en tiempo real.


Parte 4: @Binding vs @Bindable – El Cara a Cara

Para un iOS developer, distinguir cuándo usar cada uno es vital para la arquitectura de la app. Vamos a contrastar @Bindable y @Binding en SwiftUI.

Semejanzas

  1. Propósito Final: Ambos tienen el mismo objetivo: permitir que un elemento de UI (como un TextField) modifique un dato que no le pertenece directamente.
  2. Sintaxis de UI: Ambos habilitan el uso del prefijo $ para pasar la referencia de escritura a los controles de SwiftUI.
  3. Flujo de Datos: Ambos facilitan un flujo de datos bidireccional.

Diferencias Clave

Característica@Binding@Bindable
Origen del DatoGeneralmente tipos de valor (struct, Bool, String) que vienen de un @State padre.Exclusivamente para tipos de referencia (Clases) que usan la macro @Observable.
UbicaciónCasi siempre en la definición de parámetros de la Vista (var body no suele declarar bindings).Puede usarse en parámetros, pero brilla cuando se declara dentro del body para desenvolver un objeto existente.
DependenciaNo le importa si el origen es @State, @ObservedObject o @Query (SwiftData). Solo quiere un canal de lectura/escritura.Depende estrictamente del framework Observation (iOS 17+).
Propiedad“Tengo permiso para tocar este valor específico”.“Tengo permiso para crear bindings a cualquier propiedad de este objeto”.

Parte 5: Escenarios de Uso Real para el iOS Developer

Vamos a ver cómo integrar ambos en una arquitectura moderna usando SwiftUI y Xcode.

Escenario 1: La Celda de una Lista (El reino de @Bindable)

Imagina una lista de tareas (TodoItem) donde cada celda tiene un Toggle para marcarla como completada. Usando el nuevo SwiftData o @Observable, esto es trivial con @Bindable.

@Observable
class TodoItem: Identifiable {
    let id = UUID()
    var title: String
    var isDone: Bool
    
    init(title: String, isDone: Bool = false) {
        self.title = title
        self.isDone = isDone
    }
}

struct TodoListView: View {
    @State private var items = [
        TodoItem(title: "Aprender @Bindable"),
        TodoItem(title: "Refactorizar código")
    ]

    var body: some View {
        List {
            // Truco Pro: Al iterar con binding, obtenemos acceso directo
            ForEach(items) { item in
                TodoCell(item: item)
            }
        }
    }
}

struct TodoCell: View {
    // 1. Recibimos el objeto observable
    var item: TodoItem
    
    var body: some View {
        // 2. Lo hacemos 'Bindable' para poder editarlo
        @Bindable var bindableItem = item
        
        HStack {
            // 3. Usamos el binding ($)
            Toggle(isOn: $bindableItem.isDone) {
                Text(item.title)
                    .strikethrough(item.isDone)
            }
        }
    }
}

Escenario 2: Componentes Atómicos (El reino de @Binding)

Ahora imagina que quieres crear tu propio componente de Checkbox personalizado que sea reutilizable para cualquier booleano, no solo para TodoItems. Aquí @Binding sigue siendo el rey.

struct CustomCheckbox: View {
    // No le importa si es un TodoItem, un UserSettings o un State local.
    // Solo quiere un booleano.
    @Binding var isChecked: Bool
    
    var body: some View {
        Button {
            isChecked.toggle()
        } label: {
            Image(systemName: isChecked ? "checkmark.square.fill" : "square")
                .font(.title)
        }
    }
}

// Uso en la celda anterior:
// CustomCheckbox(isChecked: $bindableItem.isDone)

Lección de Arquitectura:

  • Usa @Bindable en Vistas de Pantalla o Celdas complejas que conocen tu Modelo de Datos (Clases Observable).
  • Usa @Binding en Componentes de UI genéricos (Botones, Inputs personalizados) que deben ser agnósticos al modelo de datos.

Parte 6: Errores Comunes y Mejores Prácticas

Como iOS developer, optimizar tu flujo de trabajo en Xcode es vital. Aquí hay trampas comunes al usar estas herramientas.

1. Olvidar la Macro @Observable

@Bindable no funciona con clases que conforman ObservableObject (el protocolo antiguo). Si intentas usarlo con una clase antigua, Xcode te lanzará errores crípticos. Asegúrate de migrar tus modelos a la macro @Observable si tu target es iOS 17+.

2. Usar @Bindable cuando no hay edición

Si solo vas a leer datos (mostrar un texto), no necesitas @Bindable.

// MAL (Innecesario)
struct ReadOnlyView: View {
    var user: User
    var body: some View {
        @Bindable var bUser = user // Gasto innecesario
        Text(bUser.name)
    }
}

// BIEN
struct ReadOnlyView: View {
    var user: User
    var body: some View {
        Text(user.name) // Swift sabe detectar la lectura y suscribirse
    }
}

3. Binding.constant para Previews

Al trabajar en Xcode, a menudo querrás previsualizar componentes que usan @Binding sin crear toda una lógica de estado. Usa .constant():

#Preview {
    AirplaneModeSwitch(isOn: .constant(true))
}

Para @Bindable, simplemente pasa una instancia del objeto en la preview, ya que el sistema de observación funciona automáticamente.


Conclusión: El Futuro de SwiftUI

La introducción de @Bindable no mata a @Binding. Al contrario, completan el círculo de la gestión de estado en la programación Swift.

  • @Binding sigue siendo el pegamento fundamental para conectar componentes padres e hijos, especialmente para tipos de valor y componentes de UI puros.
  • @Bindable es la herramienta moderna que necesitamos para interactuar con nuestros modelos de datos complejos basados en clases y el framework Observation.

Dominar la distinción entre @Bindable y @Binding en SwiftUI te permitirá escribir código más limpio, reducir el “boilerplate” y aprovechar al máximo las capacidades de rendimiento de iOS, macOS y watchOS. La próxima vez que te enfrentes a un error de compilación intentando pasar un objeto a un TextField, recuerda: si es una clase Observable, necesitas @Bindable. Si es un valor simple del padre, necesitas @Binding.

Tu viaje como iOS developer es una evolución constante. Adopta Observation, limpia tus vistas y deja que SwiftUI haga el trabajo pesado por ti.

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

@ViewBuilder vs struct View en SwiftUI

Next Article

@Binding vs @State en SwiftUI

Related Posts