@StateObject es un property wrapper en SwiftUI que crea y posee una instancia de ObservableObject durante todo el ciclo de vida de una View. Cuando una View aparece por primera vez en pantalla, @StateObject inicializa el objeto y lo almacena hasta que la View se elimina de la memoria. Esto garantiza que los datos no se restablezcan al reconstruir la interfaz, por ejemplo, al cambiar el tema o actualizar la View padre. Según la Documentación para Desarrolladores de Apple (2025), @StateObject debe usarse como la fuente principal de verdad (source of truth) para ObservableObject en la jerarquía de SwiftUI, mientras que las Views hijas reciben el objeto ya creado a través de @ObservedObject o @EnvironmentObject.
Puntos Clave
@StateObject es un property wrapper introducido en iOS 14 que permite a una View crear y poseer una instancia de una clase que cumpla con el protocolo ObservableObject. A diferencia de @State, que trabaja con tipos de valor (structs), @StateObject está diseñado para tipos de referencia — clases que pueden notificar a SwiftUI sobre cambios en sus propiedades.
Cuando una View usa @StateObject var viewModel: MyViewModel, SwiftUI crea automáticamente una instancia de MyViewModel cuando la View aparece por primera vez y la almacena en un almacenamiento especial del framework. En cada actualización de la View (por ejemplo, cuando cambia el estado padre), SwiftUI no recrea el objeto — usa la instancia existente hasta que la View se elimina de la jerarquía.
Según Apple WWDC Session 10137 (2024), @StateObject resuelve el problema de pérdida de datos que existía en iOS 13 cuando las Views se reconstruían, obligando a los desarrolladores a crear ObservableObject en la View padre y pasarlo a través del inicializador. Esto provocaba duplicación de código y el riesgo de recrear accidentalmente el objeto.
import SwiftUI
class CounterViewModel: ObservableObject {
@Published var count: Int = 0
func increment() {
count += 1
}
}
struct CounterView: View {
@StateObject var viewModel = CounterViewModel()
var body: some View {
VStack {
Text("Count: \(viewModel.count)")
Button("Increment", action: viewModel.increment)
}
}
}
El mecanismo de @StateObject se basa en la integración de SwiftUI con el framework Combine. Cuando un ObservableObject marca sus propiedades con el atributo @Published, SwiftUI se suscribe automáticamente a los cambios a través del publisher integrado en el protocolo ObservableObject. Cuando una propiedad publicada cambia, el objeto envía una señal a través del publisher objectWillChange, lo que desencadena un redibujado de todas las Views que observan este objeto.
SwiftUI almacena la instancia de ObservableObject en un almacenamiento especial vinculado a una instancia específica de View. Este almacenamiento se crea una vez durante el primer renderizado y existe hasta que la View se destruye. Por eso @StateObject garantiza la estabilidad de la referencia — SwiftUI gestiona la memoria automáticamente, sin depender del inicializador de la View.
Según objc.io — Thinking in SwiftUI (2025), la implementación interna de @StateObject utiliza un mecanismo similar a @State pero para tipos de referencia: SwiftUI crea una envoltura (boxing) alrededor del objeto y gestiona su ciclo de vida a través de su propio asignador, optimizado para reconstrucciones frecuentes de la jerarquía de Views.
La principal diferencia entre @StateObject y @ObservedObject radica en quién posee el objeto. @StateObject crea y almacena el objeto — es el propietario. @ObservedObject solo observa el objeto que fue creado en otro lugar y pasado a través del inicializador o una propiedad.
| Característica | @StateObject | @ObservedObject |
|---|---|---|
| Propiedad | Crea y posee el objeto | Solo observa |
| Inicialización | Dentro de la View mediante init/default | Externa, se pasa mediante parámetro |
| Ciclo de vida | Vinculado al ciclo de vida de la View | No controlado por la View |
| Recreación | No se recrea al actualizar | Puede ser reemplazado externamente |
| Versión de iOS | iOS 14+ | iOS 13+ |
La regla es simple: si la View crea el ObservableObject — usa @StateObject. Si la View solo recibe un objeto ya creado del padre — usa @ObservedObject. Violar esta regla lleva a la pérdida de datos (si se usa @ObservedObject para la propiedad) o a la creación excesiva de objetos (si se usa @StateObject para la observación).
@StateObject debe usarse en las Views que son la fuente de verdad para un conjunto específico de datos. Los escenarios típicos incluyen pantallas con su propio view model, pantallas raíz de pilas de navegación y presentaciones modales que gestionan su propio estado.
struct ProfileView: View {
@StateObject var viewModel = ProfileViewModel()
var body: some View {
NavigationStack {
Form {
TextField("Name", text: $viewModel.name)
TextField("Email", text: $viewModel.email)
Button("Save") {
viewModel.saveProfile()
}
}
.navigationTitle("Profile")
}
}
}
Inicializar @StateObject con parámetros requiere una sintaxis especial, ya que SwiftUI gestiona la creación del objeto por sí mismo. No se pueden pasar simplemente parámetros al inicializador — hay que usar un closure escapado o un método de fábrica separado.
Según Swift by Sundell (2024), el enfoque más limpio es usar un método de fábrica o closure que SwiftUI llamará cuando el objeto se cree por primera vez. Un enfoque alternativo es inicializar el ObservableObject en la View padre y pasarlo a través de @StateObject usando el inicializador estándar.
class UserViewModel: ObservableObject {
@Published var user: User
init(user: User) {
self.user = user
}
}
struct UserDetailView: View {
@StateObject var viewModel: UserViewModel
init(user: User) {
_viewModel = StateObject(wrappedValue: UserViewModel(user: user))
}
var body: some View {
Text(viewModel.user.name)
}
}
Es importante recordar que el inicializador de View con @StateObject debe usar un guion bajo antes del nombre de la propiedad (_viewModel) para acceder al property wrapper en sí, no a su valor. Este es un patrón estándar de Swift para trabajar con property wrappers en inicializadores.
El error más común es usar @ObservedObject en lugar de @StateObject para una View que debería poseer el objeto. En este caso, cada vez que se reconstruye el padre, el objeto se recrea, lo que provoca la pérdida de todos los datos acumulados. Este error es especialmente peligroso en jerarquías complejas con NavigationStack o TabView.
Para evitar estos problemas, sigue una regla simple: un @StateObject por fuente de verdad. Si los datos deben compartirse entre varias pantallas — crea @StateObject una vez en la View raíz y pásalo a través de @ObservedObject o @EnvironmentObject a los elementos hijos.
// ❌ Wrong: @ObservedObject for owning an object
struct BadView: View {
@ObservedObject var vm = ViewModel() // will be recreated on each update!
}
// ✅ Correct: @StateObject for owning
struct GoodView: View {
@StateObject var vm = ViewModel() // created once for View lifetime
}
Preguntas Frecuentes
@State trabaja con tipos de valor (structs, strings, números) y almacena el valor directamente en el almacenamiento de SwiftUI. @StateObject trabaja con tipos de referencia — clases que cumplen con ObservableObject. @State es adecuado para estados locales simples, @StateObject para objetos complejos con lógica y propiedades publicadas.
No, @StateObject solo está disponible desde iOS 14 en adelante. Para iOS 13, usa @ObservedObject y crea el ObservableObject en la View padre a través de @State con gestión manual del ciclo de vida. Una alternativa es usar @State con un struct en lugar de una clase para datos que no requieren semántica de referencia.
La View hija creará su propia copia del ObservableObject, completamente independiente de la del padre. Los cambios en uno no afectarán al otro. Esto es casi siempre un error: usa @ObservedObject para recibir un objeto del padre y @StateObject solo para crear un nuevo objeto dentro de la View.
El objeto se destruye cuando la View que lo creó se elimina completamente de la jerarquía de SwiftUI. Para una pantalla en NavigationStack, esto ocurre al hacer pop de la pila de navegación. Para una ventana modal — cuando se cierra. Para TabView — al cambiar de pestaña, si la View no está en caché.
Usa un init personalizado con acceso al property wrapper mediante guion bajo: _viewModel = StateObject(wrappedValue: MyViewModel(param: value)). Este patrón permite pasar cualquier parámetro al ObservableObject manteniendo la garantía de creación única del objeto durante la vida de la View.
Resumen
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.
Lea también