NavigationView와 NavigationStack은 네비게이션 인터페이스를 구현하기 위해 사용되지만, 각각의 특징과 사용법에는 차이점이 존재. iOS 16부터 NavigationStack을 도입하여 NavigationView의 한계를 개선하고 더 유연하고 강력한 네비게이션 시스템을 제공.
1. NavigationView
정의
- SwiftUI에서 iOS 13부터 사용 가능.
- 뷰 계층을 탐색할 수 있는 기본적인 네비게이션 컨테이너.
- 단순하고 사용하기 쉽지만, 복잡한 네비게이션 흐름에서는 한계 존재.
특징
- 정적 탐색 구조
- 선언된 뷰 계층에 따라 탐색 경로를 정적 정의.
- 초기화 동작
- NavigationLink의 destination 뷰는 탐색 여부와 관계없이 미리 초기화.
- Deprecated
- iOS 16부터 비추천(deprecated)되고, NavigationStack 사용이 권장.
기본 사용 예
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
Text("Home View")
NavigationLink(destination: DetailView()) {
Text("Go to Detail")
}
}
.navigationTitle("Home")
}
}
}
struct DetailView: View {
var body: some View {
Text("Detail View")
}
}
2. NavigationStack
정의
- iOS 16부터 새롭게 도입된 네비게이션 컨테이너.
- 동적 경로(NavigationPath)를 활용한 스택 기반 탐색 모델을 제공.
- 복잡한 상태 기반 탐색 및 경로 관리가 가능.
특징
- 스택 기반 탐색
- 탐색 경로가 스택(NavigationPath)으로 관리되어, 상태를 활용해 특정 화면으로 동적으로 전환 가능.
- 경로 중심 탐색
- navigationDestination으로 경로를 기반으로 화면을 동적으로 정의.
- 동적 초기화
- 실제 탐색이 이루어지기 전까지 뷰를 초기화하지 않음.
기본 사용 예
struct ContentView: View {
@State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
VStack {
Text("Home View")
Button("Go to Detail") {
path.append("Detail")
}
}
.navigationTitle("Home")
.navigationDestination(for: String.self) { value in
if value == "Detail" {
DetailView()
}
}
}
}
}
struct DetailView: View {
var body: some View {
Text("Detail View")
}
}
3. NavigationView vs NavigationStack
NavigationView | NavigationStack | |
도입 시기 | iOS 13 | iOS 16 |
데이터 관리 | 상태 기반 네비게이션이 제한적 | 상태(NavigationPath)를 활용한 데이터 기반 탐색 가능 |
초기화 동작 | 탐색 여부와 관계없이 초기화 | 탐색 시점에서 초기화 |
탐색 구조 | 뷰 계층 구조를 기반으로 동작 | 스택(NavigationPath) 기반 탐색 모델 |
확장성 | 복잡한 네비게이션 흐름에서 한계가 있음 | 동적이고 유연한 탐색 지원 |
Deprecated 여부 | iOS 16부터 비추천(deprecated) | 최신 네비게이션 시스템 |
커스텀 경로 지원 | 지원하지 않음 | navigationDestination으로 동적 화면 정의 가능 |
4. NavigationStack의 주요 장점
동적 경로 탐색
- NavigationPath를 활용하여 경로를 동적으로 구성하거나 수정할 수 있음.
- 복잡한 네비게이션 플로우도 쉽게 관리 가능.
@State private var path = NavigationPath()
Button("Go to Multiple Details") {
path.append("Detail1")
path.append("Detail2")
}
NavigationDestination
- NavigationStack은 경로와 관련된 여러 목적지를 한 곳에서 정의 가능.
- 특정 데이터 타입에 따라 다른 화면을 보여줄 수 있음.
.navigationDestination(for: String.self) { value in
if value == "Detail1" {
Text("Detail 1")
} else if value == "Detail2" {
Text("Detail 2")
}
}
5. SwiftUI에서 뷰의 초기화 방식
SwiftUI는 뷰 계층을 데이터 상태에 따라 동적으로 생성.
그러나 뷰를 생성할 때 실제로 화면에 렌더링되는 시점과는 다르게, SwiftUI는 다음의 이유로 뷰를 미리 초기화할 수 있음.
- 뷰 계층 탐색
- SwiftUI는 뷰의 선언적 계층을 탐색하고, 데이터 상태에 따라 필요한 뷰를 생성.
- 이 과정에서 뷰의 초기화가 필요하다고 판단되면, 화면에 나타나기 전에 초기화.
- 미리 생성
- SwiftUI는 상태 변화에 따라 화면을 업데이트할 때, 뷰가 어떻게 보일지 예측하기 위해 하위 뷰를 미리 생성.
- 특히 NavigationView 안의 뷰는 사용자가 해당 화면으로 탐색하지 않아도 초기화될 수 있음.
6. NavigationView의 초기화 동작
NavigationView를 사용하면 해당 뷰로 탐색(Navigation) 하지 않아도 초기화(init).
이유는 SwiftUI의 선언적(declarative) UI 모델과 뷰 계층 구조의 생성 방식 때문.
- 자식 뷰의 초기화
- NavigationLink로 연결된 대상 뷰(destination)는 화면에 표시되지 않아도 NavigationView가 로드될 때 초기화.
- SwiftUI는 destination을 미리 준비하여 탐색 시 뷰를 즉시 렌더링할 수 있도록 처리.
- 뷰 계층의 정적 생성
- NavigationView 내부의 뷰는 정적 선언으로 간주되어, SwiftUI는 이를 즉시 초기화하려고 시도.
- 즉, 사용자 입력(탐색)이 없어도, SwiftUI는 뷰 계층을 확인하고 이를 초기화.
예시 코드
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink(destination: DetailView()) {
Text("Go to Detail")
}
}
}
}
struct DetailView: View {
init() {
print("DetailView init")
}
var body: some View {
Text("Detail View")
}
}
- 위 코드를 실행하면, DetailView로 이동하지 않아도 DetailView init이 출력.
- SwiftUI가 NavigationLink의 destination 뷰를 초기화하기 때문.
7. NavigationStack의 동적 초기화
NavigationStack은 NavigationPath를 사용하여 탐색 상태를 관리하기 때문에, 탐색이 이루어지기 전까지 해당 뷰를 초기화하지 않음.
예시 코드
struct ContentView: View {
@State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
VStack {
Button("Go to Detail") {
path.append("Detail")
}
}
.navigationDestination(for: String.self) { value in
if value == "Detail" {
DetailView()
}
}
}
}
}
struct DetailView: View {
init() {
print("DetailView init")
}
var body: some View {
Text("Detail View")
}
}
- 이 경우, 버튼을 눌러 실제로 path.append("Detail")가 호출되고 나서야 DetailView가 초기화.
8. LazyView로 NavigationView 문제 해결
NavigationView에서도 초기화를 지연시키려면 LazyView를 활용할 수 있음.
LazyView 구현
struct LazyView<Content: View>: View {
let build: () -> Content
init(_ build: @autoclosure @escaping () -> Content) {
self.build = build
}
var body: Content {
build()
}
}
사용 예
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink(destination: LazyView(DetailView())) {
Text("Go to Detail")
}
}
}
}
- LazyView를 사용하면 실제 탐색 시점에 뷰가 초기화.
9. 왜 이런 차이가 생길까?
NavigationView의 동작
- NavigationView는 단순히 뷰 계층을 구성하는 컨테이너로 작동.
- destination에 전달된 뷰를 미리 초기화하여 탐색 시 즉시 화면에 렌더링할 수 있도록 준비.
- 이는 선언적 프로그래밍 모델의 특성과 SwiftUI의 초기 렌더링 방식 때문.
NavigationStack의 동작
- NavigationStack은 상태(NavigationPath)를 기반으로 탐색 흐름을 관리.
- 상태에 따라 필요한 뷰만 초기화되므로, 불필요한 초기화를 방지.
10. 선택 가이드
사용 상황 | 권장 컨테이너 |
단순한 네비게이션 플로우가 필요한 경우 | NavigationView (iOS 16 미만) |
iOS 16 이상에서 동적 경로를 기반으로 네비게이션해야 하는 경우 | NavigationStack |
복잡한 상태 관리와 화면 탐색이 필요한 경우 | NavigationStack |
11. 결론
- NavigationView: 간단한 네비게이션에 적합하지만, 복잡한 상태 관리에는 제약.
- NavigationStack: 동적 경로 탐색과 초기화 최적화로 복잡한 네비게이션 흐름에 적합.
- iOS 16 이상에서는 NavigationStack을 사용하는 것이 권장.
'SwiftUI' 카테고리의 다른 글
[SwiftUI] AsyncImage in SwiftUI (0) | 2024.05.13 |
---|---|
[SwiftUI] ForEach in SwiftUI (0) | 2024.05.13 |
[SwiftUI] SwiftUI의 View Rendering (0) | 2024.05.08 |
[SwiftUI] ViewModifier, modifier(_:) (0) | 2024.05.03 |
[SwiftUI] Source of Truth (0) | 2024.04.29 |