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.
Overview
StackState and StackAction are the core types for implementing stack-based navigation in TCA. StackState is a collection that holds the state of all screens in a navigation stack, while StackAction represents actions that can be sent to or from stack elements.
StackState
Declaration
public struct StackState<Element>
A list of data representing the content of a navigation stack.
Usage
Use StackState to model navigation stack state in your feature:
@Reducer
struct AppFeature {
@Reducer
enum Path {
case detail(DetailFeature)
case settings(SettingsFeature)
}
@ObservableState
struct State {
var path = StackState<Path.State>()
}
enum Action {
case path(StackActionOf<Path>)
}
var body: some ReducerOf<Self> {
Reduce { state, action in
// Core logic
}
.forEach(\.path, action: \.path)
}
}
Properties
ids
public var ids: OrderedSet<StackElementID>
An ordered set of identifiers, one for each stack element. You can use this to iterate over elements with their IDs:
for (id, element) in zip(state.path.ids, state.path) {
if element.isDeleted {
state.path.pop(from: id)
break
}
}
Subscripts
Access by ID
public subscript(id id: StackElementID) -> Element?
Access the value associated with a given ID:
if var element = state.path[id: elementID] {
// Modify element
state.path[id: elementID] = element
}
Access by Case
public subscript<Case>(
id id: StackElementID,
case path: CaseKeyPath<Element, Case>
) -> Case?
Access a specific case of an enum element:
state.path[id: 0, case: \.edit]?.alert = AlertState {
Text("Delete?")
}
Methods
pop(from:)
public mutating func pop(from id: StackElementID)
Pops the element corresponding to id from the stack, and all elements after it:
state.path.pop(from: elementID)
pop(to:)
public mutating func pop(to id: StackElementID)
Pops all elements that come after the element corresponding to id:
state.path.pop(to: elementID)
Collection Operations
StackState conforms to RandomAccessCollection and RangeReplaceableCollection, so you can use standard collection operations:
// Append elements
state.path.append(.detail(DetailFeature.State()))
// Remove all
state.path.removeAll()
// Count elements
let count = state.path.count
// Iterate
for element in state.path {
// Process element
}
StackAction
Declaration
public enum StackAction<State, Action>
A wrapper type for actions that can be presented in a navigation stack.
Cases
element
case element(id: StackElementID, action: Action)
An action sent to the associated stack element at a given identifier:
case .path(.element(id: elementID, action: .incrementButtonTapped)):
// Child handled the action
return .none
popFrom
case popFrom(id: StackElementID)
An action sent to dismiss the stack element at the given identifier:
case .path(.popFrom(id: elementID)):
// Element was popped
return .none
push
case push(id: StackElementID, state: State)
An action sent to present the given state at a given identifier. This is typically sent automatically from the view via NavigationLink(state:).
Usage Example
@Reducer
struct NavigationDemo {
@Reducer
enum Path {
case screenA(ScreenA)
case screenB(ScreenB)
}
@ObservableState
struct State {
var path = StackState<Path.State>()
}
enum Action {
case path(StackActionOf<Path>)
case popToRoot
}
var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case .path(.element(id: _, action: .screenA(.nextButtonTapped))):
state.path.append(.screenB(ScreenB.State()))
return .none
case .popToRoot:
state.path.removeAll()
return .none
case .path:
return .none
}
}
.forEach(\.path, action: \.path)
}
}
StackElementID
Declaration
public struct StackElementID: Hashable, Sendable
An opaque identifier for stack elements. IDs are automatically generated when elements are added to the stack.
Testing with IDs
In tests, you can use integer literals to reference stack element IDs:
@Test
func basics() {
var path = StackState<Int>()
path.append(42)
XCTAssertEqual(path[id: 0], 42)
path.append(1729)
XCTAssertEqual(path[id: 1], 1729)
path.removeAll()
path.append(-1)
XCTAssertEqual(path[id: 2], -1) // Generational ID
}
Note that IDs are generational in tests - they keep counting up even after elements are removed.
StackActionOf
Type Alias
public typealias StackActionOf<R: Reducer> = StackAction<R.State, R.Action>
A convenience type alias for referring to a stack action of a given reducer’s domain:
// Instead of:
case path(StackAction<Path.State, Path.Action>)
// You can write:
case path(StackActionOf<Path>)
Key Points
- Type-Safe Navigation:
StackState enforces type safety for all navigation operations
- Automatic Cleanup: Effects are automatically cancelled when elements are popped from the stack
- Deterministic IDs: Stack element IDs are predictable in tests for easier testing
- Parent Intercepts: The parent reducer can intercept and respond to child actions before they’re processed
- Deep Linking: You can programmatically build deep navigation stacks by appending multiple elements
NavigationLink(state:) - SwiftUI view for triggering navigation
forEach - Reducer operator for integrating stack state
@Presents - Macro for tree-based navigation
PresentationState - Type for optional navigation state
See Also