top of page
Writer's pictureDon Peter

Introduction to State Management in SwiftUI: @State, @StateObject and @ObservedObject

Updated: Aug 10, 2023


Introduction to State Management in SwiftUI: @State and Combine

SwiftUI is a powerful framework for building user interfaces for Apple devices. However, one of the challenges of using SwiftUI is managing state. State is the data that changes over time in your app, such as the current user's location or the contents of a shopping cart.


Using @State property wrapper in SwiftUI


There are a few different ways to manage state in SwiftUI. The simplest way is to use the @State property wrapper. The @State property wrapper allows you to store a value that can be changed within a view. When the value changes, SwiftUI will automatically update the view.


For example, let's say we have a view that displays a counter. We can use the @State property wrapper to store the current value of the counter. When the user taps a button, we can increment the counter value and then update the view.



struct CounterView: View {
  @State private var counter = 0

  var body: some View {
    Button("Increment") {
      counter += 1
    }
    Text("\(counter)")
  }
}
  

The @State property wrapper is a great way to manage simple state in SwiftUI.

However, some of the limitations of using @State are,

  • @State properties can only be used in structs. This means that you can't use @State properties in classes or enums.

  • @State properties can't be used to store complex objects. This means that you can't store objects that contain functions, closures, or other complex types in a @State property.

  • @State properties can't be changed from outside the view. This means that you can't change the value of a @State property from another view or from code that isn't part of the view hierarchy.


Using @StateObject and @ObservedObject


The code below shows how to use the @StateObject and @ObservedObject property wrappers to manage state in SwiftUI.


The GameProgress class is an ObservableObject class. This means that it conforms to the ObservableObject protocol, which allows it to be observed by other views. The points property in the GameProgress class is marked with the @Published property wrapper.


This means that any changes to the value of the points property will be automatically published to any views that are observing it.


The ButtonView struct is a view that observes the progress property. The progress property is marked with the @ObservedObject property wrapper, which tells SwiftUI that the view should observe the value of the property and update itself whenever the value changes. The ButtonView struct has a button that increments the value of the points property. When the button is tapped, the points property is incremented and the InnerView struct is updated to reflect the change.


The ContentView struct is the main view of the app. It has a progress property that is an instance of the GameProgress class. The progress property is marked with the @StateObject property wrapper, which tells SwiftUI that the property is owned by the ContentView view. The ContentView struct has a VStack that contains two views: a Text view that displays the current points, and an ButtonView view that allows the user to increment the points.


class GameProgress: ObservableObject {
    @Published var points = 0
}

struct ButtonView: View {
    @ObservedObject var progress: GameProgress

    var body: some View {
        Button("Increase Points") {
            progress.points += 1
        }
    }
}

struct ContentView: View {
    @StateObject var progress = GameProgress()

    var body: some View {
        VStack {
            Text("Your points are \(progress.points)")
            ButtonView(progress: progress)
        }
    }
}

  

Here are some key takeaways from this code:

  • The @StateObject property wrapper is used to create an object that can be observed by other views.

  • The @Published property wrapper is used to mark a property in an ObservableObject class as being observable.

  • The @ObservedObject property wrapper is used to observe a property in an ObservableObject class from another view.

  • When the value of a property that is marked with the @Published property wrapper changes, the views that are observing the property will be updated automatically.


This is a simple example of how to use the @StateObject and @ObservedObject property wrappers to manage state in SwiftUI. In a more complex app, the GameProgress class would likely be responsible for managing more than just the points. It might also be responsible for fetching data from a server or interacting with other parts of the app.


Using @EnvironmentObject


final class MyTheme: ObservableObject {
    @Published var mainColor: Color = .purple
}

struct ThemeApp: App {
    @StateObject var myTheme = MyTheme()

    var body: some Scene {
        WindowGroup {
            ThemesListView()
                .environmentObject(myTheme) // Make the theme available through the environment.
        }
    }
}

And the ThemesListView struct will be,


struct ThemesListView: View {

    @EnvironmentObject var myTheme: Theme
    
    Text("Text Title")
        .backgroundColor(myTheme.mainColor)
    
}

The code is for a SwiftUI app that uses an environment object to share a theme between views. The theme object is a MyTheme class that conforms to the ObservableObject protocol. This means that the theme object can be observed by other views.


The ThemeApp struct is the main entry point for the app. It creates a myTheme property that is an instance of the MyTheme class. The myTheme property is marked with the @StateObject property wrapper, which means that it is owned by the ThemeApp struct.


The ThemeApp struct also has a body property that returns a WindowGroup. The WindowGroup contains an ThemesListView view. The ThemesListView view is a view that displays a list of themes.


The ThemesListView view uses the environmentObject modifier to access the myTheme property. This modifier tells SwiftUI to look for the myTheme property in the environment of the ThemesListView view. If the myTheme property is not found in the environment, then a new instance of the MyTheme class will be created.


The ThemesListView view uses the myTheme.mainColor property to set the color of the list items. This means that the color of the list items will be updated automatically whenever the mainColor property of the myTheme object changes.


Using an environment object is a simple and elegant solution. We only have to create the theme object once, and it will be available to all child views automatically. This makes our code easier to read and maintain.


Conclusion

In this blog post, we have explored three different ways to manage state in SwiftUI. We have seen how to use the @State property wrapper to manage simple state, how to use the @StateObject and @ObservedObject property wrappers to manage complex state, and how to use environment objects to share state between views.


The best approach to use will depend on the specific needs of your app.

Comments


Blog for Mobile App Developers, Testers and App Owners

 

This blog is from Finotes Team. Finotes is a lightweight mobile APM and bug detection tool for iOS and Android apps.

In this blog we talk about iOS and Android app development technologies, languages and frameworks like Java, Kotlin, Swift, Objective-C, Dart and Flutter that are used to build mobile apps. Read articles from Finotes team about good programming and software engineering practices, testing and QA practices, performance issues and bugs, concepts and techniques. 

Monitor & Improve Performance of your Mobile App

 

Detect memory leaks, abnormal memory usages, crashes, API / Network call issues, frame rate issues, ANR, App Hangs, Exceptions and Errors, and much more.

Explore Finotes

bottom of page