안녕하세요 ~! SwiftUI를 공부하던 중, iOS 16 이후로 NavigationStack이 등장하게 되면서,
NavigationView가 훗날 deprecated될 예정이라는 내용을 보고, 2개가 어떻게 다른지 살펴보고자합니다.
* 공식문서: https://developer.apple.com/documentation/swiftui/navigationview
NavigationView | Apple Developer Documentation
A view for presenting a stack of views that represents a visible path in a navigation hierarchy.
developer.apple.com
1. NavigationView
- 기존 UIKit의 UINavigationController 처럼, push/pop의 방식으로 화면 전환하는 방식의 뷰입니다.
- Navigation을 구현하려면, 개발자가 NavigationLink를 통해 destination을 구성해야합니다.
2. NavigationLink
- 네비게이션을 수행하며 보여줄 화면(View)과 목적지(destination)을 컨트롤하는 뷰입니다.
- 반드시 NavigationView 혹은 NavigationStack 안에 위치해야합니다.
var body: some View {
NavigationView {
NavigationLink {
// destination: 네비게이션 링크를 통해 이동할 View
SecondNavigationView()
} label: { // label: 네비게이션을 수행할 View 구성
Text("Second Navigation 이동")
.foregroundColor(.white)
.font(.headline)
.padding()
.background(.blue)
.cornerRadius(10)
}
// 상단 페이지 제목
.navigationTitle("페이지 제목")
// navigationBarTitleDisplayMode: 페이지 제목 스타일
// .automatic: 자동, .inline - 상단에 작게, large: leading 쪽으로 크게
.navigationBarTitleDisplayMode(.automatic)
.navigationBarHidden(false) // navigationBarHidden: 상단 네비게이션 바 숨기기
// navigationBarItems : 상단 좌 우에 아이템 나타내기
.navigationBarItems(
leading: Image(systemName: "line.3.horizontal"),
trailing: Button(action: {
showSheet.toggle()
}, label: {
Image(systemName: "gear")
})
)
}
3. NavigationStack
iOS 16이후로 NavigationView가 deprecated되면서 새로 등장한 네비게이션을 제어하는 뷰입니다.
*공식문서: https://developer.apple.com/documentation/swiftui/navigationstack
NavigationStack | Apple Developer Documentation
A view that displays a root view and enables you to present additional views over the root view.
developer.apple.com
NavigationView와 NavigationStack의 큰 차이점은 아래 2가지입니다.
4. 차이점
1) .navigationDestination(for: )를 활용한 데이터 타입별 화면 전환
// MARK: - 유형 1
NavigationStack(path: $stack) {
NavigationLink("Second Navigation 이동", value: 40) // 이후 화면에 넘기려는 값
.navigationDestination(for: Int.self) { value in
SecondNavigationView()
// MARK: - 유형 2
NavigationStack {
List {
NavigationLink("첫 번째 화면", value: "첫 번째 화면")
NavigationLink("두 번째 화면", value: "두 번째 화면")
}
.navigationDestination(for: String.self) { value in
Text("현재 화면: \(value)")
}
}
// MARK: - 유형 3
NavigationStack {
// Navigation Link 에서 value 값을 넘겨주고 navigationDestination 으로 값을 넘겨 줄수 있음
// (Navigation Link 의 value type 을 파악해서 넘김
VStack (spacing: 20) {
NavigationLink(value: "String Test") {
Text("Navigation 1")
}
NavigationLink(value: true) {
Text("Navigation 2")
}
}
.navigationDestination(for: String.self, destination: { stringValue in
Text("값은: ") + Text("\(stringValue)").bold()
})
.navigationDestination(for: Bool.self, destination: { boolValue in
Text("값은: ") + Text("\(boolValue.description)").bold()
})
.navigationTitle("Navigaion Destination")
}
.font(.title)
}
}
NavigationStack에서는 NavigationLink(value:) + .navigationDestination(for:) 조합으로 데이터 타입별 화면 전환이 가능해졌습니다. (아래 예시와 같이 커스텀 구조체도 가능)
2) Path 기반 네비게이션 관리
- 여러 타입의 값 저장: 다양한 Hashable 타입의 값을 스택에 쌓을 수 있습니다.
// MARK: - Path
// Model: Path로 넘기기 위해 Hashable 프로토콜을 준수해야함
struct ProductForNav: Hashable {
var name: String
var price: Double
}
struct DateForNav: Hashable {
var name: String
var date: Date
}
struct NavigationLinkPath: View {
// NavigationPath() 는 여러개의 collection 의 데이터 타입을 가질수 있는데,
// 다른 타입의 product 와 shop 을 navigationStack 에서 navigationDestion 으로 넘기게 하려면
// NavigatioPath를 사용해서 넘겨야함
@State private var product = ProductForNav(name: "iPhone 15 Pro", price: 150.99)
@State private var date = DateForNav(name: "구매한 날짜 ", date: .now)
@State private var navPath = NavigationPath()
var body: some View {
NavigationStack(path: $navPath) {
VStack (spacing: 20) {
List {
NavigationLink(value: product) {
Text(product.name)
}
NavigationLink(value: date) {
Text(date.name)
}
}
}
.navigationTitle("Order")
// navigationDestination 에서 path 로 넘어온 Link 값을 설정함
.navigationDestination(for: ProductForNav.self) { product in
List {
Text(product.name)
Text(product.price, format: .currency(code: "USD"))
.foregroundStyle(.secondary)
}
.navigationTitle("Price Detail")
}
.navigationDestination(for: DateForNav.self) { when in
List {
Text(when.name)
Text(when.date.formatted(date: .long, time: .omitted))
.foregroundStyle(.secondary)
}
.navigationTitle("Date Detail")
}
}
.font(.title)
}
}
- 딥링킹 및 경로 복원: 특정 경로로 한 번에 이동하거나, 스택을 초기화(루트로 이동)하는 것도 손쉽게 구현할 수 있습니다.
@State var stack = NavigationPath()
var body: some View {
// 1번째 : NavigationStack을 이용한 화면 이동
NavigationStack(path: $stack) {
NavigationLink("2번째 페이지 이동", value: 40) // 이후 화면에 넘기려는 값
.navigationDestination(for: Int.self) { value in
VStack (spacing: 20) {
NavigationLink("3번째 페이지 이동", value: 100)
Text("나의 나이는 \(value) 입니다")
Button {
// stack.removeLast() // 이전페이지로 이동
stack = .init() // 초기화면으로 바로 이동
} label: {
// Text("이전페이지로 이동하기")
Text("초기화면으로 이동하기")
}
} //: VSTACK
}
} //: NAVIGATIONSTACK
stack.removeLast()를 통해 네비게이션 path 상에서 스택 형태로 쌓여있는 뷰 중 가장 마지막 뷰를 제거할 수 있고,
초기화를 통해 root view로 이동하는 것까지 가능합니다.
- 프로그램적으로 화면 이동: 버튼 클릭 등에서 직접 path에 값을 append/remove 하여 화면 전환을 제어할 수 있습니다.
// MARK: - To Root View
struct NavigationLinkToRoot: View {
@State private var navPath: [String] = []
var body: some View {
NavigationStack(path: $navPath) {
VStack (spacing: 20) {
NavigationLink(value: "View 2") {
Text("2번 뷰로 가기")
}
NavigationLink(value: "View 3") {
Text("3번 뷰로 가기")
}
}
.navigationTitle("To Root")
// path의 값을 통해 사전에 destination 설정 가능
.navigationDestination(for: String.self) { pathValue in
if pathValue == "View 2" {
NavLinkView2(navPath: $navPath)
} else {
NavLinkView3(navPath: $navPath)
}
}
}
.font(.title)
}
}
struct NavLinkView2: View {
// Binding 을 navPath 연결
// 하위 뷰에서 상위에서 @State로 선언된 navPath를 읽는것이므로 @Binding property wrapper 사용
@Binding var navPath: [String]
var body: some View {
VStack (spacing: 20) {
// navPath 에 View 3으로 value 값 넘기기
// 위의 navigationDestination에서 정의한 NavLinkView3로 이동하게 됨
NavigationLink(value: "View 3") {
Text("3번 View 가기")
}
Text("NavPath:")
Text(navPath, format: .list(type: .and, width: .narrow))
}
}
}
struct NavLinkView3: View {
@Binding var navPath: [String]
var body: some View {
VStack (spacing: 20) {
Button("Root View 로 가기") {
navPath.removeAll() // 스택을 초기화함으로써 루트로 이동
}
Text("NavPath: ")
Text(navPath, format: .list(type: .and, width: .narrow))
}
}
}