State Manipulation in SwiftUI

Views dont change - state does

If you aren’t already taking weekly programming deep dives with me, subscribe below!

Understanding State Management in SwiftUI

Managing state is a critical part of building any iOS app. SwiftUI gives us several property wrappers to handle state in different ways, so it's important to understand the differences between these wrappers to use them effectively. In today’s issue of between the brackets, we'll explore @State, @StateObject, @ObservedObject, and @EnvironmentObject in depth.

@State

@State belongs only to the view that its declared in and stays persistent in memory for as long as the view exists.

@State private var tapCount = 0 

@State tells SwiftUI to manage this property's memory for us. When the state changes, SwiftUI will automatically update any views using it.

It’s usually best to mark @State properties private, since they shouldn't be used outside their view.

@StateObject

Use @StateObject for complex state stored in an ObservableObject. The view that creates the object should mark it @StateObject. Other views that use the object should mark it @ObservedObject.

For example:

class User: ObservableObject {
  @Published var name = "Taylor"
}

struct ProfileView: View {

  @StateObject var user = User()

  var body: some View {

    Text(user.name) 

  }
}

struct SettingsView: View {
  @ObservedObject var user: User

  var body: some View {

    TextField("Name", text: $user.name)

  }  
}

The view that creates the User object marks it @StateObject. The other view marks it @ObservedObject since it doesn't own the object.

@ObservedObject

Use @ObservedObject for state stored in a reference type that needs to be shared between multiple views. The object should conform to ObservableObject and use @Published properties to notify views of changes.

For example:

class AppState: ObservableObject {

  @Published var userName = "" 
}

struct LoginView: View {

  @ObservedObject var appState: AppState

  var body: some View {

    TextField("Username", text: $appState.userName)

  }
}

struct HomeView: View {

  @ObservedObject var appState: AppState

  var body: some View {

    Text("Welcome \(appState.userName)!")

  }
} 

The AppState object is shared between views using @ObservedObject. Changes are reflected everywhere.

@EnvironmentObject

Use @EnvironmentObject for objects you want to be available globally in your app. Declare the environment object in your Scene or App and read it from any view using @EnvironmentObject.

For example:

@main
struct MyApp: App {

  @StateObject var appState = AppState()

  var body: some Scene {

    WindowGroup {

      ContentView()
        .environmentObject(appState)

    }
  }
}

struct ContentView: View {

  @EnvironmentObject var appState: AppState

  var body: some View {

    Text(appState.userName)

  }
}

The appState object is available in any view without needing to pass it around, so it’s best used for objects that hold data that need to be passed around throughout your app.

So in summary:

  • @State for local state

  • @StateObject in the source view

  • @ObservedObject in other views

  • @EnvironmentObject for global app state

[ Zach Coriarty ]