Documentation Index Fetch the complete documentation index at: https://mintlify.com/pointfreeco/swift-composable-architecture/llms.txt
Use this file to discover all available pages before exploring further.
The Todos example demonstrates how to build a full-featured list application using TCA. It showcases collections, bindings, filtering, and coordinated animations.
Overview
This example shows how to:
Manage collections of child features with forEach
Use @Bindable for two-way bindings
Filter and transform state for views
Coordinate animations with effects
Handle list operations (add, delete, move)
Implementation
Todos Reducer
Todo Reducer
View
import ComposableArchitecture
import SwiftUI
enum Filter : LocalizedStringKey , CaseIterable , Hashable {
case all = "All"
case active = "Active"
case completed = "Completed"
}
@Reducer
struct Todos {
@ObservableState
struct State : Equatable {
var editMode: EditMode = . inactive
var filter: Filter = . all
var todos: IdentifiedArrayOf<Todo.State> = []
var filteredTodos: IdentifiedArrayOf<Todo.State> {
switch filter {
case . active : return self . todos . filter { ! $0 . isComplete }
case . all : return self . todos
case . completed : return self . todos . filter (\. isComplete )
}
}
}
enum Action : BindableAction , Sendable {
case addTodoButtonTapped
case binding (BindingAction<State>)
case clearCompletedButtonTapped
case delete (IndexSet)
case move (IndexSet, Int )
case sortCompletedTodos
case todos (IdentifiedActionOf<Todo>)
}
@Dependency (\. continuousClock ) var clock
@Dependency (\. uuid ) var uuid
private enum CancelID { case todoCompletion }
var body: some Reducer<State, Action> {
BindingReducer ()
Reduce { state, action in
switch action {
case . addTodoButtonTapped :
state. todos . insert (Todo. State ( id : self . uuid ()), at : 0 )
return . none
case . binding :
return . none
case . clearCompletedButtonTapped :
state. todos . removeAll ( where : \. isComplete )
return . none
case . delete ( let indexSet) :
let filteredTodos = state. filteredTodos
for index in indexSet {
state. todos . remove ( id : filteredTodos[index]. id )
}
return . none
case . move ( var source, var destination) :
if state.filter == .completed {
source = IndexSet (
source
. map { state. filteredTodos [ $0 ] }
. compactMap { state. todos . index ( id : $0 . id ) }
)
destination =
(destination < state. filteredTodos . endIndex
? state. todos . index ( id : state. filteredTodos [destination]. id )
: state. todos . endIndex )
?? destination
}
state. todos . move ( fromOffsets : source, toOffset : destination)
return . run { send in
try await self . clock . sleep ( for : . milliseconds ( 100 ))
await send (. sortCompletedTodos )
}
case . sortCompletedTodos :
state. todos . sort { $1 . isComplete && ! $0 . isComplete }
return . none
case . todos (. element ( id : _ , action : . binding (\. isComplete ))) :
return . run { send in
try await self . clock . sleep ( for : . seconds ( 1 ))
await send (. sortCompletedTodos , animation : . default )
}
. cancellable ( id : CancelID. todoCompletion , cancelInFlight : true )
case . todos :
return . none
}
}
. forEach (\. todos , action : \. todos ) {
Todo ()
}
}
}
Key Concepts
Collections with forEach
The forEach operator manages a collection of child features:
. forEach (\. todos , action : \. todos ) {
Todo ()
}
This automatically:
Runs the Todo reducer for each element
Routes actions to the correct todo by ID
Maintains independent state for each todo
IdentifiedArray
IdentifiedArrayOf provides efficient collection management:
var todos: IdentifiedArrayOf<Todo.State> = []
// Add at beginning
state. todos . insert (Todo. State ( id : uuid ()), at : 0 )
// Remove by ID
state. todos . remove ( id : todoID)
// Remove by predicate
state. todos . removeAll ( where : \. isComplete )
Bindings
BindingReducer enables two-way bindings:
var body: some Reducer<State, Action> {
BindingReducer () // Handles all binding actions
Reduce { state, action in
// Custom logic
}
}
In the view:
@Bindable var store: StoreOf<Todo>
TextField ( "Untitled Todo" , text : $store. description )
Computed State
Derived state for filtering:
var filteredTodos: IdentifiedArrayOf<Todo.State> {
switch filter {
case . active : return self . todos . filter { ! $0 . isComplete }
case . all : return self . todos
case . completed : return self . todos . filter (\. isComplete )
}
}
Scope to filtered todos in the view:
ForEach (store. scope ( state : \. filteredTodos , action : \. todos )) { store in
TodoView ( store : store)
}
Coordinated Animations
Delay sorting to allow animations to complete:
case . todos (. element ( id : _ , action : . binding (\. isComplete ))) :
return . run { send in
try await self . clock . sleep ( for : . seconds ( 1 ))
await send (. sortCompletedTodos , animation : . default )
}
. cancellable ( id : CancelID. todoCompletion , cancelInFlight : true )
Observing Child Actions
The parent observes when a todo is completed and schedules sorting:
case . todos (. element ( id : _ , action : . binding (\. isComplete ))) :
// A todo's completion status changed
return . run { send in
try await self . clock . sleep ( for : . seconds ( 1 ))
await send (. sortCompletedTodos , animation : . default )
}
Testing
@Test
func testAddTodo () async {
let store = TestStore ( initialState : Todos. State ()) {
Todos ()
} withDependencies : {
$0 . uuid = . incrementing
}
await store. send (. addTodoButtonTapped ) {
$0 . todos = [
Todo. State ( id : UUID ( 0 ))
]
}
}
@Test
func testCompleteTodo () async {
let store = TestStore (
initialState : Todos. State (
todos : [
Todo. State ( id : UUID ( 0 ), description : "Milk" )
]
)
) {
Todos ()
}
await store. send (. todos (. element ( id : UUID ( 0 ), action : . binding (\. isComplete )))) {
$0 . todos [ id : UUID ( 0 )] ? . isComplete = true
}
await store. receive (\. sortCompletedTodos ) {
// Completed todos moved to bottom
}
}
Source Code
View the complete example in the TCA repository:
Next Steps