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 SyncUps example is a complete application that demonstrates advanced TCA patterns including navigation stacks, shared state, persistence, and device capabilities like speech recognition and timers.
Overview
This example shows how to:
Build navigation stacks with NavigationStack
Share state across features with @Shared
Persist data automatically
Integrate with device capabilities (microphone, speech)
Manage complex feature dependencies
Handle real-time updates with effects
Implementation
App Feature
Models
App View
Preview
import ComposableArchitecture
import SwiftUI
@Reducer
struct AppFeature {
@Reducer
enum Path {
case detail (SyncUpDetail)
case meeting (Meeting, syncUp : SyncUp)
case record (RecordMeeting)
}
@ObservableState
struct State : Equatable {
var path = StackState < Path. State > ()
var syncUpsList = SyncUpsList. State ()
}
enum Action {
case path (StackActionOf<Path>)
case syncUpsList (SyncUpsList.Action)
}
@Dependency (\. date . now ) var now
@Dependency (\. uuid ) var uuid
var body: some ReducerOf< Self > {
Scope ( state : \. syncUpsList , action : \. syncUpsList ) {
SyncUpsList ()
}
Reduce { state, action in
switch action {
case . path (. element ( _ , . detail (. delegate ( let delegateAction)))) :
switch delegateAction {
case . startMeeting ( let sharedSyncUp) :
state. path . append (
. record (RecordMeeting. State ( syncUp : sharedSyncUp))
)
return . none
}
case . path :
return . none
case . syncUpsList :
return . none
}
}
. forEach (\. path , action : \. path )
}
}
Key Concepts
Navigation Stacks
The app uses StackState and StackAction for navigation:
@ObservableState
struct State : Equatable {
var path = StackState < Path. State > ()
var syncUpsList = SyncUpsList. State ()
}
enum Action {
case path (StackActionOf<Path>)
case syncUpsList (SyncUpsList.Action)
}
Push a screen:
state. path . append (. record (RecordMeeting. State ( syncUp : sharedSyncUp)))
Pop the current screen:
Enum-Based Destinations
Destinations are modeled as an enum:
@Reducer
enum Path {
case detail (SyncUpDetail)
case meeting (Meeting, syncUp : SyncUp)
case record (RecordMeeting)
}
This provides exhaustive handling and type safety.
Shared State
Data is shared across features using @Shared:
@Shared (. syncUps ) var syncUps: IdentifiedArrayOf<SyncUp>
Changes automatically:
Update all observers
Persist to disk
Propagate through navigation
Persistence
Define a shared persistence strategy:
extension PersistenceKey where Self == FileStorageKey<IdentifiedArrayOf<SyncUp>> {
static var syncUps: Self {
fileStorage (. documentsDirectory . appending ( component : "sync-ups.json" ))
}
}
Data automatically loads and saves:
@Shared (. syncUps ) var syncUps: IdentifiedArrayOf<SyncUp> = []
// Automatically persisted to sync-ups.json
Device Integration
The app integrates with device capabilities:
Speech Recognition
@Dependency (\. speechRecognizer ) var speechRecognizer
return . run { send in
let result = try await speechRecognizer. recognizeAudioFile (url)
await send (. transcriptUpdated (result))
}
Timers
@Dependency (\. continuousClock ) var clock
return . run { send in
for await _ in clock. timer ( interval : . seconds ( 1 )) {
await send (. timerTicked )
}
}
Audio Recording
@Dependency (\. audioRecorder ) var audioRecorder
return . run { send in
try await audioRecorder. startRecording ( url : url)
await send (. recordingStarted )
}
Delegate Pattern
Child features communicate with parents using delegates:
// Child action
enum Action {
case delegate (Delegate)
enum Delegate {
case startMeeting (Shared<SyncUp>)
}
}
// Child sends delegate action
return . send (. delegate (. startMeeting (state.$syncUp)))
// Parent observes delegate action
case . path (. element ( _ , . detail (. delegate (. startMeeting ( let sharedSyncUp))))) :
state. path . append (. record (RecordMeeting. State ( syncUp : sharedSyncUp)))
return . none
The app handles editing with draft state:
@ObservableState
struct State {
@Presents var editSyncUp: SyncUpForm.State ?
}
// Start editing
state. editSyncUp = SyncUpForm. State ( syncUp : state. syncUp )
// Save changes
case . editSyncUp (. presented (. saveButtonTapped )) :
guard let editedSyncUp = state.editSyncUp ? .syncUp
else { return . none }
state. syncUp = editedSyncUp
state. editSyncUp = nil
return . none
Architecture
The app is organized into focused features:
AppFeature (Root)
├── SyncUpsList
│ └── SyncUp rows
└── Path (Navigation)
├── SyncUpDetail
│ ├── SyncUpForm (edit)
│ └── Past meetings
├── RecordMeeting
│ ├── Timer
│ ├── Speech recognition
│ └── Attendee tracking
└── Meeting (past meeting view)
Each feature:
Has its own state and actions
Manages its own dependencies
Can be developed and tested independently
Testing
@Test
func testRecordMeeting () async {
let clock = TestClock ()
let syncUp = SyncUp (
id : SyncUp. ID (),
attendees : [
Attendee ( id : Attendee. ID (), name : "Blob" ),
Attendee ( id : Attendee. ID (), name : "Blob Jr" ),
],
duration : . seconds ( 6 )
)
let store = TestStore (
initialState : RecordMeeting. State ( syncUp : Shared (syncUp))
) {
RecordMeeting ()
} withDependencies : {
$0 . continuousClock = clock
$0 . speechRecognizer . recognizeAudioFile = { _ in "Hello" }
}
await store. send (. task )
await clock. advance ( by : . seconds ( 3 ))
await store. receive (\. timerTicked ) {
$0 . secondsElapsed = 3
$0 . speakerIndex = 1
}
await clock. advance ( by : . seconds ( 3 ))
await store. receive (\. timerTicked ) {
$0 . secondsElapsed = 6
}
await store. receive (\. delegate . saveMeeting )
}
Source Code
View the complete example in the TCA repository:
Next Steps