TCA NavigationStackStore 사용 방법 (SwiftUI / Composable Architecture)
SwiftUI에서 NavigationStack을 사용하면 화면 이동을 비교적 간단하게 구현할 수 있다. 하지만 TCA(Composable Architecture)를 사용하는 경우에는 NavigationStack을 상태 기반(state-driven)으로 관리하는 방식이 필요하다.
이때 사용하는 것이 바로 NavigationStackStore이다.
NavigationStackStore는 SwiftUI NavigationStack과 TCA 상태 시스템을 연결해주는 도구로, StackState와 StackAction을 이용해 navigation 흐름을 관리한다.
왜 NavigationStackStore가 필요한가
SwiftUI 기본 NavigationStack은 보통 다음처럼 사용한다.
NavigationStack {
List(items) { item in
NavigationLink(value: item) {
Text(item.title)
}
}
}
.navigationDestination(for: Item.self) { item in
DetailView(item: item)
}
이 방식은 간단하지만 TCA에서는 문제가 생긴다.
- navigation 상태가 Store에 없다
- 화면 이동을 reducer에서 제어할 수 없다
- 테스트하기 어렵다
- navigation 흐름이 분산된다
그래서 TCA에서는 navigation 자체도 state로 관리하는 패턴을 사용한다.
NavigationStackStore 핵심 구성 요소
NavigationStackStore를 사용할 때 등장하는 핵심 구성 요소는 다음과 같다.
- StackState
- StackAction
- NavigationStackStore
- Path reducer
이 네 가지가 함께 navigation 흐름을 구성한다.
기본 구조
먼저 Feature에 navigation 상태를 정의한다.
@Reducer
struct TodoListFeature {
@Reducer
enum Path {
case detail(TodoDetailFeature)
}
@ObservableState
struct State: Equatable {
var items: [String] = []
var path = StackState<Path.State>()
}
enum Action {
case itemTapped(String)
case path(StackAction<Path.State, Path.Action>)
}
var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case let .itemTapped(item):
state.path.append(.detail(
TodoDetailFeature.State(title: item)
))
return .none
case .path:
return .none
}
}
.forEach(\.path, action: \.path)
}
}
여기서 중요한 부분은 StackState이다.
StackState는 NavigationStack의 경로(path)를 TCA state 안에서 관리하는 구조다.
StackState 역할
StackState는 navigation stack의 상태를 저장한다.
var path = StackState<Path.State>()
예를 들어 다음과 같은 화면 구조가 있다고 해보자.
- TodoList
- TodoDetail
- TodoEdit
사용자가 Detail → Edit로 이동하면 StackState 내부는 다음과 같은 구조가 된다.
[DetailState, EditState]
즉 NavigationStack의 stack을 그대로 state로 표현하는 것이다.
Path Reducer 정의
navigation으로 push되는 Feature들을 Path reducer 안에 정의한다.
@Reducer
enum Path {
case detail(TodoDetailFeature)
case edit(TodoEditFeature)
}
이 enum은 navigation stack에 들어갈 수 있는 화면들을 의미한다.
NavigationStackStore 사용하기
이제 View에서 NavigationStackStore를 사용한다.
import SwiftUI
import ComposableArchitecture
struct TodoListView: View {
let store: StoreOf<TodoListFeature>
var body: some View {
NavigationStackStore(
store.scope(state: \.path, action: \.path)
) {
List {
ForEach(["Apple", "Banana", "Orange"], id: \.self) { item in
Button(item) {
store.send(.itemTapped(item))
}
}
}
} destination: { state in
switch state {
case .detail:
CaseLet(
/TodoListFeature.Path.State.detail,
action: TodoListFeature.Path.Action.detail,
then: TodoDetailView.init
)
}
}
}
}
NavigationStackStore는 두 부분으로 나뉜다.
- 루트 화면
- destination 화면
destination에서는 CaseLet을 사용해 각 Feature를 연결한다.
화면 push하기
TCA에서는 reducer에서 navigation을 발생시킨다.
case let .itemTapped(item):
state.path.append(
.detail(
TodoDetailFeature.State(title: item)
)
)
return .none
append를 하면 NavigationStack에 push된다.
즉 navigation 흐름이 reducer 중심으로 관리된다.
pop 처리
뒤로 가기는 다음처럼 path를 수정하면 된다.
state.path.removeLast()
또는 특정 위치까지 pop할 수도 있다.
state.path.removeAll()
이렇게 navigation 자체가 state이기 때문에 테스트와 디버깅이 훨씬 쉬워진다.
Delegate 패턴과 함께 사용하기
실무에서는 자식 Feature에서 직접 navigation을 하지 않고 delegate 패턴을 통해 부모 reducer에 이벤트를 전달하는 구조를 많이 사용한다.
enum Action {
case delegate(Delegate)
enum Delegate {
case editRequested
}
}
그리고 부모 reducer에서 navigation을 처리한다.
case .detail(.delegate(.editRequested)):
state.path.append(
.edit(EditFeature.State())
)
return .none
이 구조는 navigation 흐름을 부모 reducer에서 관리하게 해 준다.
NavigationStackStore 장점
- navigation 상태를 state로 관리한다
- reducer에서 navigation 흐름 제어 가능
- 테스트 가능
- navigation 로직이 분산되지 않는다
- DeepLink 처리에도 유리하다
즉 navigation을 UI 로직이 아니라 상태 기반 로직으로 관리할 수 있다.
자주 하는 실수
NavigationLink를 직접 사용하는 경우
TCA에서는 NavigationLink보다 state 기반 navigation을 사용하는 것이 좋다.
path reducer를 정의하지 않는 경우
navigation stack에 들어갈 Feature는 Path reducer 안에 정의해야 한다.
forEach를 빼먹는 경우
StackState를 사용하면 반드시 다음 코드가 필요하다.
.forEach(\.path, action: \.path)
navigation 로직을 View에 두는 경우
navigation은 reducer에서 처리하는 것이 TCA 구조에 맞다.
실무에서 자주 사용하는 구조
MainFeature
├── HomeFeature
│ └── HomeDetailFeature
│
├── RecordFeature
│ └── RecordEditFeature
│
└── SettingsFeature
보통 Root Feature에서 NavigationStackStore를 관리하고 각 Feature가 delegate 이벤트를 통해 navigation을 요청하는 구조를 많이 사용한다.
정리
- NavigationStackStore는 SwiftUI NavigationStack을 TCA state와 연결하는 도구다.
- StackState로 navigation stack을 state로 관리한다.
- navigation push는 reducer에서 path.append로 처리한다.
- destination 화면은 CaseLet으로 연결한다.
- navigation 흐름을 reducer 중심으로 관리할 수 있다.
TCA (Composable Architecture) 관련 글
- TCA (Composable Architecture) 개념 정리 (Swift / iOS)
- TCA Feature 모듈 구조 설계 방법 (SwiftUI / Composable Architecture)
- TCA Reducer 구조 설계 방법 (SwiftUI / Composable Architecture)
- TCA Store scope 사용 방법 (SwiftUI / Composable Architecture)
- TCA Dependency 사용 방법 (SwiftUI / Composable Architecture)
- TCA Async / Effect 사용 방법 (SwiftUI / Composable Architecture)
- TCA BindingAction 사용 방법 (SwiftUI / Composable Architecture)
- TCA 테스트 코드 작성 방법 (SwiftUI / Composable Architecture)
- TCA NavigationStack 구조 구현 방법 (SwiftUI / Composable Architecture)
- TCA StackState 사용 방법 정리 (SwiftUI / Composable Architecture)
- TCA NavigationStackStore 사용 방법 (SwiftUI / Composable Architecture)
- TCA Delegate 패턴 화면 이동 구현 방법 (SwiftUI / Composable Architecture)
- TCA TabView 구조 설계 방법 (SwiftUI / Composable Architecture)
- TCA Optional State Navigation 패턴 정리 (SwiftUI / Composable Architecture)
'IT' 카테고리의 다른 글
| iOS 탈옥 감지 구현 방법 정리 (Swift / UIKit) (1) | 2026.03.18 |
|---|---|
| TCA Optional State Navigation 패턴 정리 (SwiftUI / Composable Architecture) (0) | 2026.03.15 |
| TCA BindingAction 사용 방법 (SwiftUI / Composable Architecture) (0) | 2026.03.15 |
| TCA Async / Effect 사용 방법 (SwiftUI / Composable Architecture) (0) | 2026.03.15 |
| TCA Reducer 구조 설계 방법 (SwiftUI / Composable Architecture) (0) | 2026.03.15 |