🔍 서론
이번에는 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 |