Introduction
SwiftUI uses a declarative approach for building UIs. However, effectively managing the application's state is crucial for building maintainable and responsive apps. In this article, I will explain two key property wrappers, @StateObject and @EnvironmentObject, used for state management in SwiftUI, along with examples.
Understanding ObservableObject
In SwiftUI, the ObservableObject protocol is key for managing changes in your app's data. It allows objects to tell views they depend on (like buttons or text fields) whenever their properties update. This is done by marking important properties with @Published. When a @Published property is changed, SwiftUI automatically refreshes any views that are connected to that object, making sure they display the latest information.
StateObject. Managing State within a view
The @StateObject property wrapper is used to create and manage the lifecycle of an ObservableObject instance within a specific view. It essentially creates a new instance of the object whenever the view is created and ensures the object is deallocated when the view is destroyed.
Example. Shopping Cart with StateObject
import SwiftUI
struct Product: Identifiable {
let id = UUID()
let name: String
let price: Double
}
struct CartItem: Identifiable {
let id = UUID()
let product: Product
var quantity: Int
}
class ShoppingCart: ObservableObject {
@Published var items: [CartItem] = []
// Add item to cart
func addItem(_ product: Product) {
if let index = items.firstIndex(where: { $0.product.id == product.id }) {
items[index].quantity += 1
} else {
items.append(CartItem(product: product, quantity: 1))
}
}
// Remove item from cart
func removeItem(_ product: Product) {
if let index = items.firstIndex(where: { $0.product.id == product.id }) {
if items[index].quantity > 1 {
items[index].quantity -= 1
} else {
items.remove(at: index)
}
}
}
// Get total price
var totalPrice: Double {
items.reduce(0) { $0 + ($1.product.price * Double($1.quantity)) }
}
}
struct ContentView: View {
@StateObject private var cart = ShoppingCart()
let products = [
Product(name: "Apple", price: 0.99),
Product(name: "Banana", price: 0.59),
Product(name: "Orange", price: 1.29)
]
var body: some View {
NavigationView {
VStack {
List(products) { product in
HStack {
Text(product.name)
Spacer()
Text("$\(product.price, specifier: "%.2f")")
Button(action: {
cart.addItem(product)
}) {
Image(systemName: "plus.circle")
}
}
}
NavigationLink(destination: CartView(cart: cart)) {
Text("View Cart (\(cart.items.count) items)")
}
.padding()
}
.navigationTitle("Products")
}
}
}
struct CartView: View {
@ObservedObject var cart: ShoppingCart
var body: some View {
VStack {
List {
ForEach(cart.items) { item in
HStack {
Text(item.product.name)
Spacer()
Text("Qty: \(item.quantity)")
Text("$\(item.product.price * Double(item.quantity), specifier: "%.2f")")
}
}
}
Text("Total: $\(cart.totalPrice, specifier: "%.2f")")
.font(.largeTitle)
.padding()
}
.navigationTitle("Shopping Cart")
}
}
@main
struct ShoppingCartApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}

Maya ChopraPosted May 24, 2024, 9:34 AM
Nice Article, <3