Property Wrapper: conceptos clave, sintaxis y ejemplos en SwiftUI

Autor: IT Sectr Publicado: 2026-06-19 Tiempo de lectura: 8 min

Property Wrapper es un mecanismo de Swift que añade una capa de lógica al acceso y modificación de propiedades sin duplicar código. En SwiftUI, los Property Wrappers se convirtieron en la base de la gestión del estado de las vistas: @State, @Binding, @ObservedObject, @StateObject y @Environment. Según la documentación de Swift (2025), los envoltorios de propiedades reducen el código repetitivo en los proyectos en un promedio del 40%. Comprender Property Wrapper es esencial para todo desarrollador iOS que quiera trabajar eficazmente con el framework.

Puntos clave

  • Property Wrapper es una construcción sintáctica de Swift que añade lógica al getter y setter de una propiedad
  • @State — envoltorio para el estado local perteneciente a una sola vista
  • @Binding — conexión bidireccional con una fuente de datos de la vista padre
  • @ObservedObject — observación de un ObservableObject externo sin posesión
  • @StateObject — creación y posesión de ObservableObject a nivel de la vista actual

¿Qué es Property Wrapper?

Property Wrapper es una construcción del lenguaje Swift, introducida en la versión 5.1, que permite encapsular la lógica de acceso a propiedades en un tipo separado. En lugar de escribir getters y setters repetitivos en cada clase, el desarrollador declara el envoltorio una vez y lo aplica mediante la anotación @ antes del tipo. Swift envuelve automáticamente la propiedad en el tipo especificado, llamando a sus métodos wrappedValue y projectedValue al leer y escribir. Según Apple (WWDC 2019), los Property Wrappers se convirtieron en una abstracción clave para SwiftUI.

Un envoltorio de propiedad es una estructura o clase con el atributo @propertyWrapper. Internamente, dicho tipo debe implementar la propiedad wrappedValue, que devuelve y establece el valor real. El compilador de Swift reemplaza los accesos a la propiedad original por llamadas a wrappedValue, ocultando completamente la implementación del código llamante. Adicionalmente, se puede definir projectedValue — una proyección accesible mediante el símbolo $.

La ventaja de los Property Wrappers reside en la reutilización de la lógica. Por ejemplo, se puede crear un envoltorio para validación de email, almacenamiento en caché de valores o sincronización con almacenamiento — y aplicarlo a cualquier propiedad del proyecto. En SwiftUI, este concepto se utiliza en todas partes: cada mecanismo de gestión de estado se implementa como un Property Wrapper separado.

¿Cómo funciona Property Wrapper en Swift?

Al declarar una propiedad con la anotación @WrapperType var value: T, el compilador de Swift transforma el código. Crea una instancia de WrapperType y genera el acceso a la propiedad a través de wrappedValue. El código fuente let x = value se convierte en let x = _value.wrappedValue, y value = newValue en _value.wrappedValue = newValue. Esta transformación ocurre en tiempo de compilación, sin sobrecarga en tiempo de ejecución.

swift
@propertyWrapper
struct Capitalized {
    private var text: String

    var wrappedValue: String {
        get { text }
        set { text = newValue.capitalized }
    }

    init(initialValue: String) {
        text = initialValue.capitalized
    }
}

El listado muestra el envoltorio Capitalized, que convierte automáticamente una cadena a formato con mayúscula inicial. Al asignar un valor, el setter llama a capitalized antes de guardarlo. Ahora cualquier propiedad con la anotación @Capitalized almacenará solo texto con el formato correcto. Este enfoque elimina por completo la duplicación de código de validación y formato.

La proyección (projectedValue) es un canal de comunicación adicional accesible mediante el prefijo $. En SwiftUI, esta funcionalidad se utiliza en todas partes: $state proporciona Binding, y $observedObject devuelve Published.Publisher. La proyección permite que el envoltorio ofrezca una interfaz extendida sin cambiar el acceso básico al valor.

Principales Property Wrappers en SwiftUI

SwiftUI incluye cinco Property Wrappers incorporados para la gestión del estado: @State, @Binding, @ObservedObject, @StateObject y @Environment. Cada uno resuelve una tarea específica y se utiliza en diferentes escenarios. @State está diseñado para datos locales simples, @Binding — para pasar una referencia a datos a vistas hijas, @ObservedObject y @StateObject — para objetos complejos, @Environment — para valores del sistema de la jerarquía.

WrapperPropósitoPosesión
@StateEstado local de una sola vistaVista actual
@BindingConexión bidireccional con el padreVista padre
@ObservedObjectObservación de un objeto externoPropietario externo
@StateObjectCreación de ObservableObjectVista actual
@EnvironmentValores del sistema de la jerarquíaEntorno SwiftUI

La elección de un Property Wrapper específico depende de la fuente de datos y su ciclo de vida. Si los datos pertenecen a una sola vista y no son necesarios para componentes hijos — use @State. Si una vista hija necesita modificar los datos del padre — use @Binding. Para objetos utilizados en múltiples vistas, son adecuados @ObservedObject y @StateObject.

@State: estado local de la vista

@State es un Property Wrapper para almacenar el estado local dentro de una sola vista. SwiftUI gestiona automáticamente la memoria para las propiedades @State y redibuja la vista en cada cambio. @State es adecuado para tipos simples (String, Int, Bool, enum) y estructuras que pertenecen exclusivamente a la vista actual. Cuando el valor cambia, SwiftUI vuelve a ejecutar la propiedad body.

swift
struct CounterView: View {
    @State private var count: Int = 0

    var body: some View {
        VStack {
            Text("Recuento: \(count)")
            Button("Incrementar") {
                count += 1
            }
        }
    }
}

En el ejemplo, la propiedad @State count almacena el valor actual del contador. SwiftUI crea un área de almacenamiento para esta propiedad en el heap y la vincula al ciclo de vida de CounterView. Al pulsar el botón, count aumenta en 1, SwiftUI detecta el cambio y vuelve a ejecutar body, mostrando el nuevo valor. Importante: @State no debe usarse para tipos de referencia complejos — para eso están @StateObject y @ObservedObject.

@Binding: conexión bidireccional entre vistas

@Binding crea una referencia a una fuente de datos que pertenece a otra vista. Binding no almacena un valor por sí mismo — lee y escribe datos a través de @State, @StateObject u otro Binding pasado desde el padre. Esto permite que los componentes hijos modifiquen el estado del ancestro sin poseer los datos directamente y sin callbacks.

swift
struct ToggleSwitch: View {
    @Binding var isOn: Bool

    var body: some View {
        Toggle("Switch", isOn: $isOn)
    }
}

En el listado, ToggleSwitch recibe @BindingBool de la vista padre. El padre crea @State var isToggleOn = false y pasa $isToggleOn al inicializador de ToggleSwitch. Cuando el usuario cambia el interruptor dentro de la vista hija, el cambio se refleja inmediatamente en el @State del padre. El mecanismo Binding elimina por completo la necesidad de delegados o closures para pasar cambios hacia arriba en la jerarquía.

@ObservedObject y @StateObject: datos externos

@ObservedObject es un Property Wrapper para observar una instancia de ObservableObject pasada desde fuera. La vista no posee este objeto — se crea en el componente padre o se inyecta a través de Environment. Cuando cualquier propiedad @Published dentro del ObservableObject cambia, SwiftUI redibuja todas las vistas suscritas mediante @ObservedObject.

@StateObject — un envoltorio para crear y poseer un ObservableObject directamente en la vista. A diferencia de @ObservedObject, @StateObject garantiza una única instancia del objeto durante todo el ciclo de vida de la vista. Incluso si SwiftUI recrea la estructura de la vista (lo que ocurre con frecuencia), @StateObject conserva el objeto existente y no vuelve a llamar al inicializador.

swift
class UserSettings: ObservableObject {
    @Published var username: String = "Guest"
}

struct ProfileView: View {
    @StateObject var settings = UserSettings()

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

struct ChildProfileView: View {
    @ObservedObject var settings: UserSettings

    var body: some View {
        Text("Hola, \(settings.username)")
    }
}

En el ejemplo, ProfileView crea UserSettings mediante @StateObject, convirtiéndose en el propietario del objeto. ChildProfileView recibe la misma instancia mediante @ObservedObject — observa pero no gestiona el ciclo de vida. Cuando username cambia, ambas vistas se actualizan. Si ChildProfileView usara @StateObject en lugar de @ObservedObject, en cada renderizado se crearía una nueva instancia con el valor inicial.

La regla clave: @StateObject se utiliza en la vista que crea el objeto (fuente de verdad), mientras que @ObservedObject se usa en la vista que recibe un objeto ya creado del padre. Violar esta regla conduce a la pérdida de estado o a recreaciones inesperadas de datos.

Creación de un Property Wrapper personalizado

Swift permite crear Property Wrappers personalizados para cualquier lógica repetitiva de acceso a propiedades. Simplemente declare una estructura o clase con el atributo @propertyWrapper e implemente wrappedValue. A continuación se muestra el envoltorio UserDefaultsWrapper, que sincroniza automáticamente el valor con UserDefaults.

swift
@propertyWrapper
struct UserDefaultsWrapper<T> {
    let key: String
    let defaultValue: T

    var wrappedValue: T {
        get { UserDefaults.standard.object(forKey: key) as? T ?? defaultValue }
        set { UserDefaults.standard.set(newValue, forKey: key) }
    }
}

struct AppConfig {
    @UserDefaultsWrapper(key: "theme", defaultValue: "light")
    var theme: String
}

El envoltorio UserDefaultsWrapper utiliza un genérico T para funcionar con cualquier tipo de datos compatible con UserDefaults. El getter lee el valor por clave, el setter lo escribe. Aplicar @UserDefaultsWrapper(key:defaultValue:) a la propiedad theme la vincula automáticamente al almacenamiento — toda la lógica de UserDefaults queda oculta dentro del envoltorio. Este es un ejemplo típico de reducción de código repetitivo con Property Wrappers.

Al crear envoltorios personalizados, es importante considerar el rendimiento. Dado que el getter y el setter se llaman cada vez que se accede a la propiedad, no se deben colocar operaciones pesadas de E/S en wrappedValue. Para almacenamiento de datos asíncrono, es mejor combinar Property Wrappers con ObservableObject y @Published.

Preguntas frecuentes

¿Cuál es la diferencia entre @State y @StateObject?

@State está diseñado para tipos simples (String, Int, Bool) y estructuras, mientras que @StateObject es para tipos de referencia que implementan ObservableObject. @State almacena el valor directamente en SwiftUI, @StateObject gestiona una instancia de clase en el heap.

¿Se puede usar @Binding sin @State?

Sí, @Binding se puede crear a partir de @StateObject, @ObservedObject o de otro Binding mediante la proyección $. También se puede inicializar Binding desde ObservableObject a través de $object.$publishedProperty o desde InlineBinding mediante Binding.constant(value).

¿Qué Property Wrapper elegir para datos utilizados en toda la aplicación?

Para datos globales, use @EnvironmentObject o inyecte ObservableObject a través de EnvironmentValues. @StateObject es adecuado para la vista raíz con posterior transmisión mediante @ObservedObject a los componentes hijos.

¿Por qué @ObservedObject puede recrear datos?

@ObservedObject no posee el objeto — si la vista padre se recrea y pasa una nueva instancia, @ObservedObject cambiará a ella. Para evitar la pérdida de estado, la vista propietaria debe usar @StateObject.

¿Se puede crear un Property Wrapper para operaciones asíncronas?

Sí, pero es más fácil usar una combinación de ObservableObject con @Published y funciones asíncronas dentro de la clase. Property Wrapper es síncrono por naturaleza — wrappedValue se calcula en cada acceso, lo que no es adecuado para operaciones de larga duración.

Resumen

  • Property Wrapper — construcción sintáctica de Swift 5.1 para encapsular la lógica de acceso a propiedades sin duplicación de código
  • @State — estado local para tipos simples, redibuja automáticamente la vista al cambiar
  • @Binding — referencia a los datos de la vista padre sin posesión, accesible mediante la proyección $
  • @StateObject — posesión de ObservableObject con garantía de instancia única por ciclo de vida
  • @ObservedObject — observación de ObservableObject pasado desde el componente padre
  • @Environment — acceso a los objetos del entorno del sistema SwiftUI
  • Envoltorios personalizados se crean mediante el atributo @propertyWrapper y la implementación de wrappedValue

Desarrollaremos una aplicación móvil llave en mano

IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.

Discutir el proyecto

Lea también