A menudo, cuando estoy inmerso en un proyecto de SwiftUI, me doy cuenta de que empiezo a usar @StateObject, @State, @EnvironmentObject y @ObservedObject de manera casi automática, sin detenerme a pensar en los casos específicos para los que fueron creados. Es como si me hubiera acostumbrado tanto a estos patrones que a veces olvido por qué los uso en cada situación.
Y eso es un error.
Es por eso que decidí escribir este artículo. Para poner en orden mis ideas y, de paso, compartir una reflexión sobre el propósito y el uso adecuado de cada uno. Al final, SwiftUI nos ofrece múltiples formas de gestionar el estado, pero elegir el camino adecuado para cada contexto puede marcar una gran diferencia en la eficiencia y mantenimiento del código.
Aquí exploraremos cada una de estas opciones, con ejemplos prácticos que me han ayudado a entender cuándo y por qué utilizarlas.
@State: Para propiedades locales
El modificador @State es perfecto para propiedades que pertenecen solo a una vista específica. Es ideal para situaciones simples, donde no necesitamos compartir el estado con otras vistas. Funciona como una especie de almacén local para variables que cambian dentro de la misma vista.
Ejemplo:
struct ContentView: View {
@State private var tapCount = 0
var body: some View {
Button("Tap count: \(tapCount)") {
tapCount += 1
}
}
}
Aquí, @State le dice a SwiftUI que gestione el valor de tapCount dentro de la vista. Cada vez que el usuario hace clic en el botón, la variable se incrementa, y SwiftUI se encarga de actualizar la vista. Esta propiedad es completamente local a ContentView, por lo que no puede compartirse con otras vistas.
@ObservedObject: Para datos compartidos
Cuando los datos necesitan ser compartidos y actualizados entre múltiples vistas, @ObservedObject entra en juego. A diferencia de @State, aquí trabajamos con clases que pueden ser observadas, lo que permite que varias vistas se sincronicen y se mantengan actualizadas.
Ejemplo:
class UserSettings: ObservableObject {
@Published var username: String = "Anónimo"
}
struct ContentView: View {
@ObservedObject var settings = UserSettings()
var body: some View {
VStack {
Text("Nombre de usuario: \(settings.username)")
Button("Cambiar nombre") {
settings.username = "Nuevo Usuario"
}
}
}
}
Aquí vemos cómo @ObservedObject funciona con una clase UserSettings que implementa el protocolo ObservableObject. Cualquier cambio en la propiedad username será notificado automáticamente a todas las vistas que la estén observando. De esta manera, @ObservedObject es ideal para gestionar estados que deben ser compartidos entre varias vistas.
@StateObject: El dueño del objeto
@StateObject es un patrón que usamos cuando una vista necesita crear y gestionar el ciclo de vida de un objeto. Es esencial cuando queremos asegurarnos de que SwiftUI retiene y no destruye accidentalmente el objeto.
Ejemplo:
class Counter: ObservableObject {
@Published var count = 0
}
struct ContentView: View {
@StateObject var counter = Counter()
var body: some View {
VStack {
Text("Counter: \(counter.count)")
Button("Incrementar") {
counter.count += 1
}
}
}
}
En este caso, @StateObject asegura que la instancia de Counter sea creada y gestionada por ContentView. La diferencia clave con @ObservedObject es que aquí, la vista es la responsable del ciclo de vida del objeto.
@EnvironmentObject: Para datos globales
@EnvironmentObject es útil cuando queremos compartir datos entre múltiples vistas sin tener que pasarlos explícitamente como parámetros de una vista a otra. Funciona muy bien para estados globales, como las preferencias de usuario o configuraciones de la aplicación.
Ejemplo:
class AppSettings: ObservableObject {
@Published var isDarkMode: Bool = false
}
struct ContentView: View {
@EnvironmentObject var settings: AppSettings
var body: some View {
VStack {
Toggle("Modo Oscuro", isOn: $settings.isDarkMode)
if settings.isDarkMode {
Text("Modo Oscuro Activado")
} else {
Text("Modo Oscuro Desactivado")
}
}
}
}
// Ejemplo de cómo inyectar el EnvironmentObject en el ContentView
@main
struct MyApp: App {
var settings = AppSettings()
var body: some Scene {
WindowGroup {
ContentView().environmentObject(settings)
}
}
}
En este ejemplo, AppSettings se inyecta en el árbol de vistas mediante environmentObject. Cualquier vista que necesite acceder a la configuración de la aplicación puede hacerlo sin pasar explícitamente el objeto a cada nivel de la jerarquía.

Reflexiones
Después de repasar todos estos patrones, me doy cuenta de que a menudo me he encontrado en situaciones donde no estaba del todo seguro de cuál utilizar, o simplemente elegía uno por inercia. Sin embargo, detenerse a reconsiderar cada opción ayuda a construir aplicaciones más claras, eficientes y fáciles de mantener.
Entender cuándo usar @State, cuándo decantarse por @ObservedObject o cuándo encapsular lógica en un @StateObject es clave para mejorar como desarrolladores de SwiftUI. Espero que este repaso te sea útil la próxima vez que, como a mí, te entren dudas sobre cuál utilizar en tu código. Ahora, cuando me encuentro con estas decisiones, puedo tomar una pausa y elegir la mejor herramienta para cada caso, sabiendo que cada una tiene un propósito claro.