🍎 iOS/SwiftUI

[SwiftUI] NavigationView vs NavigationStack

dev_zoe 2025. 5. 30. 23:56
반응형

안녕하세요 ~! 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))
        }
    }
}

 

반응형