SwiftUI Bites #01 – State Management & Property Wrappers – when to use what

SwiftUI State Property Wrappers

SwiftUI Bites is a short-form series for developers (including UIKit converts)
explaining SwiftUI concepts quickly, clearly, and with real-world examples.

Today, we’ll take a closer look at state management in SwiftUI using property wrappers.
You’ve likely seen them before — and probably used them — but this article brings everything together in one place.

Quick Help: Choosing the Right SwiftUI Property Wrapper

If you’re already familiar with SwiftUI’s state property wrappers and just need a quick refresher, start with the decision model and matrix below.
For everyone else, we’ll walk through the details and examples right after.

SwiftUI State Property Wrappers — Quick Lookup

Property WrapperDecision Flow QuestionData TypeOwnershipWhen the View Updates
@EnvironmentObjectIs this app-wide state? → YesReference (ObservableObject)App / SceneAny published change anywhere in the app
@StateNot app-wide → Value typeValue (Bool, Int, String, struct)ViewLocal value mutation
@StateObjectObject → Created by this viewReference (ObservableObject)ViewPublished changes from the object
@ObservedObjectObject → Created externallyReference (ObservableObject)Parent / externalPublished changes from the object
@BindingDoes a child need to modify it? → YesUsually value typesParent viewWrites propagate back to the owner

Understanding SwiftUI State Property Wrappers

This article is based on a small demo app that shows each SwiftUI state property wrapper in action.
You can find the full, runnable project on GitHub:

👉 https://github.com/Wooder/SwiftUIBites-StateManagement

Local Value State: @State and @Binding

Let’s start with the simplest form of state in SwiftUI: local value state.

This is the kind of state that:

  • belongs to a single view
  • represents simple values like Bool, Int, or String
  • drives local UI updates

Typical examples include counters, toggles, selections, or temporary UI state.


@State: View-owned value state

@State is used when a view owns a piece of value-type state.

@State private var count: Int = 0

Even though SwiftUI views are value types, @State is stored externally by SwiftUI.
This means the value survives view re-creations and triggers a view update whenever it changes.

Key characteristics of @State:

  • the state is owned by the view
  • only the owning view should mutate it
  • changes automatically re-render the view

If a value is local to a view and does not need to be shared broadly, @State is usually the correct choice.


@Binding: Write access to parent-owned state

Often, a child view needs to modify state that is owned by its parent.

This is where @Binding comes in.

@Binding var count: Int

A binding does not store state itself.
Instead, it provides read/write access to state that is owned elsewhere.

In practice, this means:

  • the parent keeps ownership using @State
  • the child receives a binding using @Binding
  • changes in the child propagate back to the parent automatically

This preserves a clear ownership model while still allowing child views to participate in state changes.


Putting it together

In the demo app, this pattern is used to implement a simple dice roll:

  • The parent view owns the value using @State
  • A child view updates the value via @Binding
  • SwiftUI re-renders the UI whenever the value changes

This keeps responsibilities clear:

  • ownership stays with the parent
  • interaction logic can live in the child

Demo project reference

You can find a complete working example in the demo project:

  • DiceView.swift — owns the value using @State
  • DiceViewActions.swift — modifies it via @Binding

Running the app and interacting with this view makes the data flow immediately visible.


Common pitfall

A frequent mistake is trying to use @State in both the parent and the child.

This creates two independent states that quickly fall out of sync.

If a child needs to modify parent-owned value state, the correct solution is almost always @Binding.


In the next section, we’ll move from value types to reference types and look at how ownership works for ObservableObject using @StateObject and @ObservedObject.

Reference State and Ownership: @StateObject vs @ObservedObject

Not all state in SwiftUI is a simple value.

As soon as you work with reference types — typically view models conforming to ObservableObjectownership and lifecycle become the deciding factors.

This is where @StateObject and @ObservedObject come into play.


ObservableObject: Reference-based state

A typical view model in SwiftUI looks like this:

final class CounterViewModel: ObservableObject {
@Published private(set) var count: Int = 0

func increment() {
count += 1
}
}

Key points:

  • it’s a reference type
  • it emits change notifications via @Published
  • multiple views can observe the same instance

Unlike value state, reference state has a lifecycle — and SwiftUI needs to know who owns it.


@StateObject: The owning view

Use @StateObject when a view creates and owns an observable object.

@StateObject private var vm = CounterViewModel()

@StateObject guarantees that:

  • the object is created only once
  • the instance survives view re-renders
  • SwiftUI manages its lifecycle correctly

In other words:

  • this view is the source of truth
  • this view is responsible for creating the object

If a view creates a view model, @StateObject is the correct choice.


@ObservedObject: Observing external state

Child views often need access to a view model that is owned elsewhere.

In that case, use @ObservedObject.

@ObservedObject var vm: CounterViewModel

With @ObservedObject:

  • the view does not own the object
  • it assumes the object already exists
  • it simply reacts to published changes

This avoids accidental re-creation of the object and keeps ownership explicit.


Putting it together

In the demo app, this pattern is used for a simple counter:

  • CounterView creates and owns the view model using @StateObject
  • CounterActions receives the same instance via @ObservedObject
  • when the counter changes, SwiftUI re-renders both views automatically

This separation keeps responsibilities clear:

  • one owner
  • many observers
  • predictable lifecycle behavior

Demo project reference

You can explore this pattern in the demo project:

  • CounterView.swift — creates the view model using @StateObject
  • CounterActions.swift — observes it via @ObservedObject

Running the app and tapping the buttons makes the ownership model visible in action.


Common pitfall

A very common mistake is using @ObservedObject to create a view model:

@ObservedObject var vm = CounterViewModel() // ❌

This causes the object to be recreated whenever the view is re-rendered, leading to lost state and subtle bugs.

If a view creates the object, it must use @StateObject.


In the next section, we’ll look at app-wide state and how @EnvironmentObject allows you to share data across multiple, unrelated views without manual prop drilling.

App-Wide State: @EnvironmentObject

So far, all examples used explicit data flow:

  • state is created in a parent
  • passed down to children via initializers

This works well — until state needs to be shared across many unrelated views.

That’s where @EnvironmentObject comes in.


What @EnvironmentObject is for

@EnvironmentObject is used for app-wide or feature-wide state that:

  • is needed by many views
  • is not tied to a single view hierarchy
  • should not be passed through multiple initializers

Typical examples include:

  • user sessions
  • authentication state
  • app settings
  • feature flags

Injecting the environment object

An environment object is created at a high level in the app and injected into the view hierarchy.

@main
struct SwiftUIBites_StateManagementApp: App {
@StateObject private var session = UserSession()

var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(session)
}
}
}

Here:

  • the app creates and owns the object using @StateObject
  • the object is injected once at the root
  • all child views can access it without explicit wiring

Reading from the environment

Any view in the hierarchy can read the injected object using @EnvironmentObject.

@EnvironmentObject private var session: UserSession

When the environment object changes:

  • SwiftUI automatically re-renders all dependent views
  • no manual updates are required

This makes @EnvironmentObject feel almost “global” — but it’s still scoped to the view hierarchy.


Writing to the environment

Environment objects are not read-only.

In the demo app, the profile view updates the session state:

TextField("Username", text: $session.username)

When the user changes the username:

  • the environment object updates
  • all other views observing it are re-rendered automatically

This makes @EnvironmentObject ideal for shared, mutable app state.


Demo project reference

You can see this pattern in action in the demo project:

  • SwiftUIBites_StateManagementApp.swift — injects the environment object
  • ProfileView.swift — modifies the shared state
  • CounterView.swift and DiceView.swift — read from it

Running the app and switching between views makes the shared nature of the state very clear.


Common pitfalls

Forgetting to inject the environment object

If a view uses @EnvironmentObject but the object is not injected, the app will crash at runtime.

Always ensure the object is provided at the root of the hierarchy.


Overusing @EnvironmentObject

Not every shared value belongs in the environment.

If state is:

  • only used by a parent and its direct children
  • feature-local
  • short-lived

Passing it explicitly or using @State / @Binding is usually the better choice.


Mental model

Think of @EnvironmentObject as:

  • shared ownership
  • implicit access
  • explicit responsibility

Use it sparingly, but confidently, when state truly belongs to the app as a whole.


Wrapping up

At this point, you’ve seen all core SwiftUI state property wrappers in action:

  • local value state with @State
  • shared value access with @Binding
  • reference state ownership with @StateObject
  • reference state observation with @ObservedObject
  • app-wide shared state with @EnvironmentObject

Together, these tools form a complete and predictable state management model for SwiftUI apps.

Summary

SwiftUI’s state property wrappers are less about syntax and more about ownership, scope, and data flow.

Once you understand who owns a piece of state and who is allowed to change it, the choice of property wrapper becomes straightforward.

As a rule of thumb:

  • Use @State for local, view-owned value state
  • Use @Binding to give a child write access to parent-owned value state
  • Use @StateObject when a view creates and owns an observable object
  • Use @ObservedObject when a view observes an object owned elsewhere
  • Use @EnvironmentObject for shared, app-wide state

The decision model and demo project show that SwiftUI state management is not magic — it’s a small set of consistent rules applied in the right places.

If you ever feel unsure which wrapper to use, start by asking:

  • Is this state local or shared?
  • Is it a value or a reference?
  • Who owns it?

Answering those questions will almost always lead you to the correct solution.

Unexpected Spotlight: How a Simple Scanner for iOS “Required Reason API” Changes caught the Developer Community’s Attention

As an iOS developer, staying ahead of Apple’s ever-evolving privacy changes is not just a task but a responsibility. That’s why I embarked on a journey to create the “iOS Required API Scanner” tool (https://github.com/Wooder/ios_17_required_reason_api_scanner), aiming to prepare myself and fellow developers for Apple’s upcoming privacy updates.

Little did I anticipate the response it would receive from the iOS developer community. Suddenly, what started as a personal endeavor to ensure compliance with Apple’s guidelines gained unexpected attention. The outpouring of support, feedback, and collaboration from fellow developers has been nothing short of remarkable.

This experience has reaffirmed my belief in the strength and the positive spirit of the iOS developer community. It’s a community where individuals come together, share knowledge, and support one another in navigating the complexities of app development.

I am incredibly grateful for the opportunity to contribute to this vibrant community and humbled by the recognition the “iOS Required API Scanner” has received.

Thank you to the amazing iOS developer community for your support and encouragement. Here’s to many more shared successes in the future.

Preparing Your iOS App for the “Required Reason API”

Prepare your app for iOS 17:

This post is about the iOS App Privacy Manifest and the Required Reason API.

In this exclusive article, we dive into the App Privacy Manifest and the Required Reason API . Learn how to get your app ready for Apples new privacy measures.

https://jochen-holzer.medium.com/38f2d12bbce5?source=friends_link&sk=d146c22f3e18c6551231f4b55c934b05

Also check https://github.com/Wooder/ios_17_required_reason_api_scanner to scan Xcode Swift projects for privacy compliance effortlessly.

Essential Utilities Every iOS Developer Should Have

Essential Utilities for iOS development

Are you an iOS developer looking for the best tools to get the job done? Look no further! In this article, we will be exploring the top 5 essential utilities for iOS developers. From debugging to testing, these utilities are sure to help you develop faster and more efficiently.

Learn more in this post at medium.com: https://jochen-holzer.medium.com/essential-utilities-for-ios-developers-part-1-fabe6f5ddd24?source=friends_link&sk=4b1a338fb5c6a14e52472fe8138ffcbb

SonarQube Pull Request Decoration with GitLab

Sonarqube Pull Request Decoration with GitLab

Reviewing pull requests is one of the daily (sometimes tiresome) tasks of software developers. Parts of the review can be automated so that the reviewer can concentrate on the essentials such as architecture and business logic.

Learn how your team can save review time with SonarQube Pull Request Decoration.

Read more in my post on medium.com:

https://jochen-holzer.medium.com/sonarqube-pull-request-decoration-with-gitlab-17d99c894174

App Tracking Transparency and IDFA in iOS 14.5

App Tracking Transparency
App Tracking Transparency – IDFA – Identifier for Advertising

What is App Tracking Transparency and IDFA?

Starting with the launch of iOS 14.5, apps have to request authorization from the user in order to access the Identifier For Advertiser (IDFA) of the device (opt-in). Until now, access to IDFA was allowed by default (opt-out). This has a big impact on the app ad and attribution industry.

Learn more in my blog post on the inovex website:

https://www.inovex.de/de/blog/app-tracking-transparency-and-idfa-in-ios-14-5/