The Store and SwiftUI Integration
Drive views from a TCA store.
The Store and SwiftUI Integration is a free Swift Academy lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Swift Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
What Is a Store?
The Store is the runtime object that holds your feature's state and runs its reducer. A SwiftUI view observes a store to read state and sends actions to it to trigger changes.
The store is the bridge between your pure reducer logic and the live, interactive UI.
import ComposableArchitecture
import SwiftUI
// A Store holds State, runs the Reducer,
// and is observed by SwiftUI views.The StoreOf Type Alias
StoreOf<Feature> is a convenient alias for a store specialized to a feature's state and action types. A view typically holds one as a stored property.
So a counter view stores a StoreOf<CounterFeature>.
import ComposableArchitecture
import SwiftUI
struct CounterView: View {
let store: StoreOf<CounterFeature>
var body: some View {
Text("Count: \(store.count)")
}
}Creating a Store
You build a store with the Store initializer, providing the initial state and the reducer to run. This is usually done once near the app's entry point.
The trailing closure returns an instance of your feature.
import ComposableArchitecture
import SwiftUI
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
CounterView(
store: Store(initialState: CounterFeature.State()) {
CounterFeature()
}
)
}
}
}Reading State Directly
With modern observation, a view reads state straight off the store using dot syntax, such as store.count. Because state is @ObservableState, SwiftUI re-renders only the parts that depend on changed values.
No extra wrapper is required for simple reads.
import ComposableArchitecture
import SwiftUI
struct CounterView: View {
let store: StoreOf<CounterFeature>
var body: some View {
VStack {
Text("Count: \(store.count)")
}
}
}Sending Actions
To change state, a view sends an action with store.send(.someAction). The store feeds the action to the reducer, which mutates state and may start effects.
Here buttons send increment and decrement actions.
import ComposableArchitecture
import SwiftUI
struct CounterView: View {
let store: StoreOf<CounterFeature>
var body: some View {
HStack {
Button("-") { store.send(.decrementTapped) }
Text("\(store.count)")
Button("+") { store.send(.incrementTapped) }
}
}
}Bindings From the Store
For SwiftUI controls that need two-way bindings, TCA lets you derive a Binding from the store when state uses bindable actions. The control updates state by sending a binding action.
Here a TextField binds to a name field.
import ComposableArchitecture
import SwiftUI
struct FormView: View {
@Bindable var store: StoreOf<FormFeature>
var body: some View {
TextField("Name", text: $store.name)
}
}The Legacy WithViewStore
Before observation, views wrapped their UI in WithViewStore to read state and obtain a viewStore to send actions. You will still see this in older code.
The observe closure selects which state the view depends on.
import ComposableArchitecture
import SwiftUI
struct LegacyCounter: View {
let store: StoreOf<CounterFeature>
var body: some View {
WithViewStore(store, observe: { $0 }) { viewStore in
Text("Count: \(viewStore.count)")
}
}
}Choosing What to observe
In the legacy style, the observe closure narrows the state the view watches, so it re-renders only when those values change. Observing the whole state is simplest; observing a subset optimizes large views.
Modern @ObservableState handles this automatically, but understanding observe helps when reading older code.
import ComposableArchitecture
import SwiftUI
// Observe just the count to limit re-renders:
WithViewStore(store, observe: { $0.count }) { viewStore in
Text("\(viewStore)")
}Sending Actions With Payloads
Actions carrying associated values are sent by supplying the value. This is how a view forwards user input that the reducer needs.
Here selecting a row sends the chosen id.
import ComposableArchitecture
import SwiftUI
struct ListView: View {
let store: StoreOf<ListFeature>
var body: some View {
Button("Pick 42") {
store.send(.rowSelected(id: 42))
}
}
}Animating Actions
You can attach an animation to a sent action so the resulting state change animates. Pass an animation argument to send.
This keeps animation concerns at the call site while state stays plain data.
import ComposableArchitecture
import SwiftUI
Button("Toggle") {
store.send(.toggleTapped, animation: .default)
}Putting the View Together
A complete TCA view holds a StoreOf, reads state with dot syntax, and sends actions from controls. The reducer does the rest, keeping the view thin and declarative.
This separation is what makes TCA views easy to read and to preview.
import ComposableArchitecture
import SwiftUI
struct CounterView: View {
let store: StoreOf<CounterFeature>
var body: some View {
VStack {
Text("Count: \(store.count)")
Button("Increment") { store.send(.incrementTapped) }
}
}
}Quick Check: Driving the UI
Recall how a SwiftUI view triggers a state change in TCA.
Recap: The Store and SwiftUI
You connected a feature to SwiftUI:
- A
StoreOf<Feature>holds state and runs the reducer. - You create a store with an initial state and the feature reducer.
- Modern observation lets you read state with dot syntax like
store.count. - Views change state by calling
store.send(.action), optionally with a payload or animation. - The legacy
WithViewStoreand itsobserveclosure appear in older code.
Next you will give your reducer side effects and injectable dependencies.
import ComposableArchitecture
import SwiftUI
// Recap: StoreOf holds state, view reads with dot syntax,
// store.send(.action) drives every change.Frequently asked questions
Is the “The Store and SwiftUI Integration” lesson free?
Yes — the full text of “The Store and SwiftUI Integration” is free to read here on the web, and the Swift Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Swift Academy course, upgrade to CoddyKit PRO.
What will I learn in “The Store and SwiftUI Integration”?
Drive views from a TCA store. You practise Swift Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Swift Academy?
No prior experience is required. Swift Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “The Store and SwiftUI Integration” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Swift Academy lesson?
Yes. Every Swift Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- State, Action, and Reducer
- The Store and SwiftUI Integration
- Effects and Dependencies
- Composing and Testing Features