# Navigation 적용 가이드
> **기본 네비게이션** : push, pop 과 같이 1 depth 이동을 뜻합니다.
> **커스텀 네비게이션** : popToRoot, 또는 특정 화면으로의 복잡한 이동 같은 네비게이션 흐름을 뜻합니다.
## 네비게이션 적용
```swift
func navigationDestination<V>(
isPresented: Binding<Bool>,
@ViewBuilder destination: () -> V
) -> some View where V : View
```
- 위 메서드를 이용하여 다음화면을 정의합니다.
### 기본 네비게이션
```swift
struct LaunchScreen {
@State private var isPresentRealNameCheckView: Bool = false
var body: some View {
VStack {
Text("A 화면")
Button {
isPresentRealNameCheckView = true
} label: {
Text("실명인증 버튼")
}
}
.navigationDestination(isPresented: $isPresentRealNameCheckView) {
RealNameCheckView(
viewModel: .init(isJoin: false, publicKey: publicKey, consent: consent),
isPresentSheet: $isPresentRealNameCheckView)
}
}
}
```
### 기본 네비게이션(커스텀 네비게이션 과정에 포함되는 화면일 경우)
```swift
struct LaunchScreen {
@Environment(\.injected) private var injected: DIContainer //*1
@State private var routingState: Routing = .init() //*2
private var routingBinding: Binding<Routing> { //*3
$routingState.dispatched(to: injected.appState, \.routing.launchScreen)
}
var body: some View {
VStack {
Text("어떤화면")
Button {
routingBinding.toRealNameCheckView.wrappedValue = true
} label: {
Text("실명인증 버튼")
}
}
.navigationDestination(isPresented: routingBinding.toRealNameCheckView) {
RealNameCheckView(
viewModel: .init(isJoin: false, publicKey: publicKey, consent: consent),
isPresentSheet: $isPresentRealNameCheckView
)
.inject(injected) //*7
}
.onReceive(routingUpdate) { //*4
routingState = $0
}
}
}
extension LaunchScreen {
struct Routing: Equatable { //*5
var toRealNameCheckView = false
}
}
extension LaunchScreen {
var routingUpdate: AnyPublisher<Routing, Never> { //*6
injected.appState.updates(for: \.routing.launchScreen)
}
}
```
<details>
<summary>
별표(*)한 부분에 대한 세부 내용
</summary>
- `*1 injected` : 외부에서 주입된 의존성 입니다. 외부에서부터 전달되었기 때문에 이전 화면들 에서의 상태값을 모두 가지고 있습니다.
- `*2 routingState` : `LaunchScreen` 의 `Routing` 객체를 @State 변수로 선언합니다.
- `*3 routingBinding` : `routingState`의 변화를 주입된 의존성에 전달 후 업데이트 하는 Binding 변수입니다.
- `*6 routingUpdate` : `Routing` 타입을 발행하는 Publisher 타입입니다. 뷰에서 `.onReceive()` 메서드를 통해 값이 발행될 때 액션을 정의해줄 수 있습니다.
- `*4 .onReceive()` : `Routing` 타입의 값이 발행되면 `routingState`에 해당 값을 할당해줍니다. 그럼 `routingState` 를 사용하는 `LaunchScreen` 내부의 모든 View타입에 업데이트가 일어납니다.
- `*5 Routing 객체`
- 각 뷰는 자신이 가지는 화면전환 정보를 Routing 객체로 관리할 수 있습니다.
- 아래는 AppState에서 관리하는 정보들을 중첩타입으로 선언한 내용입니다.
```swift
struct AppState: Equatable {
var routing = ViewRouting()
var auth = Auth()
}
extension AppState {
struct ViewRouting: Equatable {
// MARK: Entries
var launchScreen = LaunchScreen.Routing()
var initView = InitView.Routing()
var homeView = HomeView.Routing()
// MARK: Portfolio-Tab
var portfolioTab = PortfolioTabView.Routing()
var portfolioDetailView = PortfolioDetailView.Routing()
var purchaseAgreeView = Portfolio_PurchaseAgreeView.Routing()
var purchaseStep1NewView = Portfolio_PurchaseStep1NewView.Routing()
// MARK: Magazine-Tab
var magazineTab = MagazineTabView.Routing()
// MARK: Wallet-Tab
var walletTab = WalletTabView.Routing()
// MARK: Setting-Tab
var settingTab = SettingTabView.Routing()
}
struct Auth: Equatable {
var shouldLogout = false
}
}
```
- 현재는 routing 정보와 auth 정보를 관리하고 있습니다.
- routing 정보 내부에 각 뷰의 `Routing` 객체를 변수로 가지고 있습니다.
- 각각의 뷰에 keyPath로 접근하여 값을 업데이트 시켜줄 수 있습니다.
- 지금처럼 내부적으로 keyPath로 접근하도록 만들어놓은 Store 타입을 사용하면, `bulkUpdate()` 메서드를 이용해 간편하게 값을 업데이트 시켜줄 수 있습니다.
- 업데이트 된 값은 *4(`.onReceive()`) 메서드가 걸려있는 곳으로 전해져 해당 뷰에 영향을 미칠 수 있습니다.
- 위처럼 특정 View의 화면전환 정보를 Routing 객체로 분리하게 되면, 어느 뷰에서나 이 정보에 접근하여 수정을 할 수 있는 상태가 됩니다.
- 즉, 어디서나 수정되길 원하는 화면전환 정보는 Routing 객체로 옮기고, 나머지는 기존처럼 @State private var isPresent... 형식으로 사용해도 무방합니다.
</details>
<br>
### 커스텀 네비게이션
```
위 화면에서 "실명인증 버튼" 을 눌러 RealNameCheckView로 이동하고, 또 비슷한 방식으로
그 하위뷰로 이동을 여러번 해서 `LastView` 까지 이동했다고 가정해봅니다. 다시 실명인증 버튼이
있는 `LaunchScreen` 화면으로 돌아오기 위해선 어떻게 해야할까요?
`LaunchScreen.Routing` 이 가지는 화면전환 정보인 `toRealNameCheckView` 를
`false`로 만들어주면 됩니다
```
```swift
struct LastView {
@Environment(\.injected) private var injected: DIContainer
var body: some View {
VStack {
Text("마지막 화면")
Button {
injected.bulkUpdate {
$0.routing.launchScreen.toRealNameCheckView = false
}
} label: {
Text("처음으로 돌아가기 버튼")
}
}
}
}
```
- 위 처럼 명령을 해주기 위해 전제되어야 할 조건은, `@Environment(\.injected) private var injected` 이 상태값이 이전 화면들의 상태값을 가지고 있어야합니다.
- 그러려면 navigation 이동 시, DestinationView.inject(injected) 와 같이 의존성을 넘겨주어야 합니다. *7 처럼 처리해주면 됩니다.