SwiftUI

[SwiftUI] NavigationView vs NavigationStack

yujaehui 2024. 5. 9. 21:00

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는 다음의 이유로 뷰를 미리 초기화할 수 있음.

  1. 뷰 계층 탐색
    • SwiftUI는 뷰의 선언적 계층을 탐색하고, 데이터 상태에 따라 필요한 뷰를 생성.
    • 이 과정에서 뷰의 초기화가 필요하다고 판단되면, 화면에 나타나기 전에 초기화.
  2. 미리 생성
    • SwiftUI는 상태 변화에 따라 화면을 업데이트할 때, 뷰가 어떻게 보일지 예측하기 위해 하위 뷰를 미리 생성.
    • 특히 NavigationView 안의 뷰는 사용자가 해당 화면으로 탐색하지 않아도 초기화될 수 있음.

6. NavigationView의 초기화 동작

NavigationView를 사용하면 해당 뷰로 탐색(Navigation) 하지 않아도 초기화(init).

이유는 SwiftUI의 선언적(declarative) UI 모델과 뷰 계층 구조의 생성 방식 때문.

  1. 자식 뷰의 초기화
    • NavigationLink로 연결된 대상 뷰(destination)는 화면에 표시되지 않아도 NavigationView가 로드될 때 초기화.
    • SwiftUI는 destination을 미리 준비하여 탐색 시 뷰를 즉시 렌더링할 수 있도록 처리.
  2. 뷰 계층의 정적 생성
    • 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