DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Because the DevOps movement has redefined engineering responsibilities, SREs now have to become stewards of observability strategy.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Related

  • Achieving High Genericity in Code
  • Build an AI Browser Agent With LLMs, Playwright, Browser Use
  • Implementing OneLake With Medallion Architecture in Microsoft Fabric
  • A Practical Guide to Augmenting LLM Models With Function Calling

Trending

  • How to Introduce a New API Quickly Using Micronaut
  • Introducing Graph Concepts in Java With Eclipse JNoSQL, Part 2: Understanding Neo4j
  • Monoliths, REST, and Spring Boot Sidecars: A Real Modernization Playbook
  • Securing the Future: Best Practices for Privacy and Data Governance in LLMOps
  1. DZone
  2. Coding
  3. Tools
  4. Beginners Guide to SwiftUI State Management

Beginners Guide to SwiftUI State Management

Delve into the intricacies of state management in SwiftUI, exploring different strategies you can employ to utilize Apple’s powerful state management APIs.

By 
Sridhar Rao Muthineni user avatar
Sridhar Rao Muthineni
·
Oct. 30, 24 · Tutorial
Likes (1)
Comment
Save
Tweet
Share
2.7K Views

Join the DZone community and get the full member experience.

Join For Free

State management is a fundamental concept in app development that involves tracking and updating data that influences the user interface. In SwiftUI, this is particularly crucial due to its declarative nature. To effectively leverage SwiftUI’s capabilities, it’s essential to grasp the various approaches to state management and why they are necessary.

This article will delve into the intricacies of state management in SwiftUI. We’ll explore different strategies that programmers can employ to utilize Apple’s powerful state management APIs. Before diving into SwiftUI-specific techniques, let’s examine the broader context of UI programming and understand why state management is indispensable.

Imperative Programming: UIKit, AppKit, and WatchKit

Before SwiftUI, iOS and tvOS developers relied on UIKit, macOS developers used AppKit, and watchOS developers worked with WatchKit. These frameworks, all based on the imperative programming paradigm, offered event-driven UI development.

Key Improvements

  • Clarity: The sentence explicitly states which frameworks were used for each platform.
  • Context: It mentions that these frameworks were based on the imperative programming paradigm.
  • Conciseness: The sentence is streamlined while maintaining essential information.

Declarative Programming: SwiftUI

SwiftUI leverages the declarative programming approach, allowing developers to define the desired outcome, and the framework handles the underlying implementation details.

In the example below, if we instruct SwiftUI to display a welcome message when logged in and a login button when logged out, it will automatically update the view to reflect changes in authentication state.

A major advantage of learning and using SwiftUI is that it allows for cross-platform development for the different operating systems within the Apple ecosystem — no more having to learn four different frameworks if you want to build an app that has components on Apple Watch, Apple TV, MacBook Pro, and finally your iPhone.

It is important to know that SwiftUI does not replace UIKit. Instead, it is built on top of providing and an additional layer of abstractions for us.

State in SwiftUI

State refers to the current data that a view or a group of views relies on to present the UI. In SwiftUI, when the state changes, the UI automatically reflects these changes. This is because SwiftUI is declarative, meaning you describe what the UI should look like based on the current data, and SwiftUI handles updating the UI when the data changes.

There are several ways to handle state in SwiftUI:

  1. @State, used to manage readonly local state
  2. @Binding, used to manage read/write state
  3. @ObservableObject, used to manage external data models
  4. @StateObject, used to manage object lifecycle within a view

Let’s explore these types in detail one by one.

First, let's create an example iOS project that we will change to explore different state types.

By default, you will get the following view:

Default view

@State

@State is used to manage local state within a single view. It is the simplest and most frequently used state management tool in SwiftUI. When you mark a property with @State, SwiftUI takes ownership of that data and ensures the view re-renders when the state changes.

Change the ContentView as follows:

Change the ContentView using @State

Let’s explore the code:

  1. We declared a private count variable initializing it to 0.
  2. We annotated the variable with @State, telling SwiftUI to observe the changes to this variable and update the view accordingly.
  3. In the body, we created a Text label that displays the count.
  4. Below the label, we added a button, and in the action, we incremented the variable.

You don’t have to run the project to see it working: you can tap the increment button in the preview and it will work.

As button is pressed, count is incremented and Text label is updated with the current value.

So, @State annotation is declaring the count variable as an state. Whenever count is updated, SwiftUI checks if any UI element is using it and updates the view accordingly.

As an experiment, try to remove @State from the count variable. You will encounter an error.

Error encountered when removing @State from the count variable


The error says: “Left side of mutating operator isn’t mutable: ‘self’ is immutable”.

What does this mean? count is variable, so it should have no problems updating. But if you look closely, the error says "self is immutable," which means that our SwiftUI’s view is immutable and cannot be changed like this. @State makes our SwiftUI views mutable.

Two-Way Binding via @State

In the previous example, our Text element was reading the count value, but the increment was not done via some UI element. Instead, it was done via our own implementation we provided to button action.

There are SwiftUI elements that directly change the data, e.g., Toggle or TextField. In such a case, we can provide our @State variable following with $. This is called binding.

Change the view to add two HStacks: one with Text and TextField, the other with Text.

Changing the view to add two HStacks


In the code above,

  1. We added a new state variable name of type String.
  2. We created two HStacks, one with TextField and another one displaying entered text.
  3. The display Text uses the same format to display name as we used for displaying counter.
  4. Notice how TextField is taking $name. This is called binding.
  5. If you type a name in the TextField, the updated name will be reflected instantly in the Text.

If you Option+Click the TextField element, a declaration popup comes up with TextField details. Notice the type of text Binding<String>. We will explore the binding further in the next section.

@Binding

In the previous example, we used TextField’s text binding to update the state of our view ContentView. So, if we look closely, what is happening here is that the child element (i.e., TextField) is updating the state of the parent (ContentView). So, @Binding is used mainly in a scenario where a child element wants to update the state of its parent element.

@Binding creates a connection with the parent’s state without owning the state itself.

As an example, let’s create parent/child views and update the state of parent from child view.

Create parent/child views and update the state of parent from child view.


Let’s dissect the code:

  1. We created our custom view named ToggleSwitch.
  2. ToggleSwitch has an isOn boolean variable marked with @Binding.
  3. @Binding tells the SwiftUI that the isOn variable is an external state that can be updated by the view.
  4. In the ParentView, we declared the isOn boolean variable just like we did before.
  5. We passed the isOn variable in ToggleSwitch preceded with $, informing that it is a two-way binding; i.e., state can be changed by the child view.

@ObservableObject

While @State is great for simple and local state, more complex data models often need to be shared across multiple views. In this case, SwiftUI introduces the ObservableObject protocol combined with the @ObservedObject property wrapper. This allows you to create external data models that viewers can observe for changes.

To use the observable object, we need to know the following:

  • A class that conforms to the ObservableObject protocol can hold data that is shared between views.
  • It uses the @Published property wrapper to notify views when the data changes.

Lets create a model class named ProfileModel that holds the data and a corresponding view class named ProfileView.

Create a model class named ProfileModel that holds the data and a corresponding view class named ProfileView


Let’s go through the details:

  1. ProfileModel is a simple class holding profile data.
  2. The class conforms to the ObservableObject protocol.
  3. All the properties are marked with @Published.
  4. ProfileView class contains a variable of ProfileModel class marked with @ObservableObject.
  5. @Published properties of ProfileModel are used as state and binding.
  6. Any change to properties via UI elements is reflected in the detail section as well.

We kept editing and displaying data on the same screen for simplicity’s sake, but in real world applications, these can be two different screens and the ProfileModel can be owned by a central (possibly a singleton) object external to the view.

@StateObject

Introduced in SwiftUI 2.0, @StateObject is used to create and manage the lifecycle of an ObservableObject within a view. It ensures that the object is only created once and is not re-created unnecessarily during re-renders.

The main difference between @StateObject and @ObservedObject is that @StateObject is responsible for creating the object, while @ObservedObject is used when the object is created elsewhere (like in a parent view).

Implementation-wise, it’s quite similar to @ObservableObject. 

@StateObject implementation


Use @StateObject when a view is responsible for creating and owning the ObservableObject.

It ensures the object is only created once, even when the view is re-rendered.

@Environment

For data that should be shared with many views in your app, SwiftUI gives us the @EnvironmentObject property wrapper. This lets us share model data anywhere it’s needed, while also ensuring that our views automatically stay updated when that data changes.

Just like @ObservedObject, you never assign a value to an @EnvironmentObject property. Instead, it should be passed in from elsewhere, and ultimately you’re probably going to want to use @StateObject to create it somewhere.

However, unlike @ObservedObject, we don’t pass our objects into other views by hand. Instead, we send the data into a modifier called environmentObject(), which makes the object available in SwiftUI’s environment for that view plus any others inside it.

  • Note: Environment objects must be supplied by an ancestor view — if SwiftUI can’t find an environment object of the correct type you’ll get a crash. This applies for previews too, so be careful.

As an example of environment object:

Example of environment object


In the above code:

  1. We created an ObservableObject class named ScoreBoard that holds our score.
  2. ScoreView class displays our score.
  3. Note how ScoreView is extracting the ScoreBoard variable from the environment automatically by just declaring the property wrapper @EnvironmentObject.
  4. ScoringView is the main view that creates ScoreBoard object, update its values, and also passes it to the SwiftUI environment so that it is available to other views.

Best Practices and Common Pitfalls

  1. Use the right tool:
    • @State for simple, view-local state
    • @Binding for parent-child communication
    • @ObservedObject or @StateObject for managing more complex, shared data models
    • @EnvironmentObject for global or shared app-wide state
  2. Avoid overusing @StateObject or @ObservedObject: Only recreate objects when necessary to avoid performance issues or unnecessary re-renders.
  3. Avoid unnecessary UI re-renders: When possible, isolate state updates to the smallest view hierarchy possible.

Conclusion

In SwiftUI, state management is a fundamental aspect of building dynamic and interactive apps. By using the correct tools (@State, @Binding, @ObservedObject, @StateObject, and @EnvironmentObject), you can efficiently manage your app’s state, ensuring a smooth and scalable user experience.

  • SwiftUIStateExamples Repo
Imperative programming UI Data (computing) Apple iOS Tool

Published at DZone with permission of Sridhar Rao Muthineni. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Achieving High Genericity in Code
  • Build an AI Browser Agent With LLMs, Playwright, Browser Use
  • Implementing OneLake With Medallion Architecture in Microsoft Fabric
  • A Practical Guide to Augmenting LLM Models With Function Calling

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!