
🔍 서론
이번에는 iOS17 이상에서 사용되는 NavigationStack을 활용한 pop to root 코드를 구현해 볼 것이다.
iOS16 이하에서 사용되는 NavigationView로는 isActive로 pop to root를 구현할 수 있지만, NavigationStack에서는 path를 이용해서 pop to root를 구현한다.
이 방식은 처음 보면 복잡하게 느낄 수 있지만, path 관련 코드만 잘 정리해서 작성해 두면 프로젝트 코드 전체 부분에서 깔끔하게 pop to root 기능을 적용할 수 있다.
📱 NavigationStack Pop to Root
결론부터 말하면, NavigationStack은 path라는 경로에 뷰의 값을 채우면서 스택이 쌓이는데, 이 경로를 모두 없애면 루트 뷰로 돌아가진다.
먼저 프로젝트의 구조는 아래와 같다.

그다음, 뷰들을 컨트롤할 Observable 클래스를 아래와 같이 만들어준다.
@Observable class NavigationControlTower { var paths = NavigationPath() @ViewBuilder func navigate(to view: Views) -> some View { switch view { case .View1: View1() case .View2: View2() case .View3: View3() } } func push(_ view: Views) { paths.append(view) } func pop() { paths.removeLast() } func popToRoot() { paths.removeLast(paths.count) } } enum Views { case View1 case View2 case View3 }
위의 클래스는 NavigationStack의 경로로 사용할 path 변수를 가지며, navigate 메서드를 통해 지정한 뷰를 리턴한다.
또한 경로를 조작하는 push, pop, popToRoot와 같은 메서드도 구현하였다.
다음으로 ContentView 코드이다.
struct ContentView: View { @State private var navControlTower = NavigationControlTower() var body: some View { NavigationStack(path: $navControlTower.paths) { navControlTower.navigate(to: .View1) .navigationDestination(for: Views.self) { view in navControlTower.navigate(to: view) } } .environment(navControlTower) } }
위의 코드에서는 방금 만든 NavigationControlTower을 상태 클래스로 등록하고, 편의성을 위해 environment를 사용하여 환경 변수에 등록해 준다.
또한, path에 아까 구현한 paths를 바인딩하고, 이 ContentView에서는 View1을 내포하고 있으므로 뷰를 paths 경로에 추가하지 않고 반환만 시키는 navigate메서드를 사용하여 반환받을 뷰 이름인 View1을 명시해 준다.
이 뷰가 뷰 스택의 시작 부분이므로 navigationDestination을 활용하여 뷰가 paths에 추가될 때 어떻게 처리될지를 정의한다.
위에서 구현한 navigationDestination의 동작 과정은 아래와 같다.
navPush 메서드로 paths에 뷰 추가
👇
navigationDestination이 paths에 추가된 뷰를 감지하여 매개변수 view에 삽입
👇
매개변수 view가 navigate 메서드의 인자로 삽입
👇
navigate 메서드가 삽입된 인자와 일치하는 뷰 반환
참고로, 여기서 ContentView와 같이 navigate 메서드를 바로 사용하면 paths에 경로가 추가되지 않는다. (그냥 화면에 뷰만 띄우는 역할)
이제 차례로 View1에서 View3까지 나열하겠다.
struct View1: View { @Environment(NavigationControlTower.self) var navControlTower: NavigationControlTower var body: some View { Text("View 1") .font(.title) .padding(.bottom, 50) Button { navControlTower.push(.View2) } label: { Text("View2로 이동") } } }
struct View2: View { @Environment(NavigationControlTower.self) var navControlTower: NavigationControlTower var body: some View { Text("View 2") .font(.title) .padding(.bottom, 50) Button { navControlTower.push(.View3) } label: { Text("View3로 이동") } } }
struct View3: View { @Environment(NavigationControlTower.self) var navControlTower: NavigationControlTower var body: some View { Text("View 3") .font(.title) .padding(.bottom, 50) Button { navControlTower.popToRoot() } label: { Text("루트 뷰로 이동") } } }
각 뷰에서 다음 뷰로 넘어가기 위해 NavigationLink를 사용하는 것이 아니라, paths에 경로를 넣으면서 뷰를 띄우기 위해 Button을 사용하였다.
마지막 뷰에서는 popToRoot() 메서드를 호출하면 paths에 있는 모든 경로가 다 지워지면서 루트 뷰로 돌아간다.
실행 영상

'iOS > SwiftUI' 카테고리의 다른 글
SwiftUI View Hierarchy & Navigation Hierarchy와 Environment의 관계 (0) | 2024.10.10 |
---|---|
SwiftUI @Bindable VS @State 차이점 (iOS17+ Observable Macro) (0) | 2024.08.09 |