[SwiftUI] NavigationLink VS NavigationStack

2025. 10. 22. 15:41·Apple/SwiftUI
728x90
반응형

안녕하세요! 피피아노입니다 🎵

 

SwiftUI로 앱을 개발하다 보면 화면 전환(Navigation)을 구현해야 하는 순간이 반드시 있습니다. 이때 가장 많이 접하게 되는 2가지 개념이 NavigationLink와 NavigationStack입니다. 

 

둘 다 화면 전환을 할 때 쓰이는 것까지는 알겠는데, 정확히 뭐가 어떻게 다르고 언제 뭘 써야 하는지 헷갈리기도 하고 이번 기회에 확실하게 알고 넘어가기 위해서 글을 정리하게 됐습니다.

 

NavigationLink: 화면 전환 트리거

NavigationLink는 네비게이션 프레젠테이션을 제어하는 뷰입니다.

 

쉽게 말해서 그냥 사용자가 탭하면 다음화면으로 이동하게 하는 버튼입니다.

 

애플 공식 문서에 적혀있는 내용을 보면 아래처럼 나와 있습니다.

"People click or tap a navigation link to present a view inside a NavigationStack or NavigationSplitView."

 

즉, NavigationLink는 반드시 NavigationStack이나 NavigationSplitView 내부에 있어야 작동합니다. 단독으로는 쓸 수 없습니다.

https://developer.apple.com/documentation/swiftui/navigationlink

 

NavigationLink | Apple Developer Documentation

A view that controls a navigation presentation.

developer.apple.com

 

2가지 사용 방식

NavigationLink는 크게 2가지 방식으로 사용할 수 있습니다.

 

1. Destination 방식: 목적지 뷰를 직접 제공

가장 간단한 방식입니다. 목적지 뷰를 직접 클로저에 제공합니다.

struct ColorDetail: View {
    var color: Color
    
    var body: some View {
        color.navigationTitle(color.description)
    }
}

// 사용 예시
NavigationStack {
    List {
        NavigationLink("Mint") { 
            ColorDetail(color: .mint) 
        }
        NavigationLink("Pink") { 
            ColorDetail(color: .pink) 
        }
        NavigationLink("Teal") { 
            ColorDetail(color: .teal) 
        }
    }
    .navigationTitle("Colors")
}

 

특징

  • 간단하고 직관적
  • 링크와 뷰가 직접 연결
  • 소규모 네비게이션에 적합

2. Value 방식(PresentaionLink): 데이터 기반 네비게이션

데이터 값을 전달하고, navigationDestination이 그 값을 받아서 뷰를 생성하는 방식입니다.

NavigationStack {
    List {
        NavigationLink("Mint", value: Color.mint)
        NavigationLink("Pink", value: Color.pink)
        NavigationLink("Teal", value: Color.teal)
    }
    .navigationDestination(for: Color.self) { color in
        ColorDetail(color: color)
    }
    .navigationTitle("Colors")
}

 

애플 공식 문서에는 이렇게 나와 있습니다.

"Separating the view from the data facilitates programmatic navigation because you can manage navigation state by recording the presented data."

 

특징

  • 뷰와 데이터를 분리하여 프로그래밍 방식 네비게이션 가능
  • 상태 관리가 쉬움
  • 타입 안전성 보장
  • 대규모 앱에서 권장되는 방식

레이블 커스터마이징

NavigationLink의 외관은 자유롭게 커스터마이징을 할 수 있습니다.

// 1. 텍스트만 사용 (가장 기본)
NavigationLink("Work Folder") {
    FolderDetail(id: workFolder.id)
}

// 2. Label 사용 (아이콘 + 텍스트)
NavigationLink {
    FolderDetail(id: workFolder.id)
} label: {
    Label("Work Folder", systemImage: "folder")
}

// 3. 완전히 커스텀 레이블
NavigationLink(value: folder) {
    HStack {
        Image(systemName: "folder.fill")
            .foregroundColor(.blue)
        VStack(alignment: .leading) {
            Text(folder.name)
                .font(.headline)
            Text("\(folder.itemCount) items")
                .font(.caption)
                .foregroundColor(.gray)
        }
    }
}

 

프로그래밍 방식으로 제어하기

Value 방식을 사용하면 코드로 네비게이션을 제어할 수 있습니다.

"You can use the array to observe the current state of the stack. You can also modify the array to change the contents of the stack."
struct ColorsView: View {
    // 스택 상태를 추적하는 배열
    @State private var colors: [Color] = []
    
    var body: some View {
        NavigationStack(path: $colors) {
            VStack {
                List {
                    NavigationLink("Mint", value: Color.mint)
                    NavigationLink("Pink", value: Color.pink)
                    NavigationLink("Teal", value: Color.teal)
                }
                
                Button("파란색으로 바로 이동") {
                    showBlue()
                }
            }
            .navigationDestination(for: Color.self) { color in
                ColorDetail(color: color)
            }
            .navigationTitle("Colors")
        }
    }
    
    // 프로그래밍 방식으로 네비게이션
    func showBlue() {
        colors.append(.blue)
    }
}

배열을 수정하면 네비게이션이 자동으로 변경됩니다.

 

NavigationSplitView와 함께 사용하기

NavigationLink는 iPad와 macOS의 분할 뷰에서도 사용됩니다.

struct ColorSplitView: View {
    let colors: [Color] = [.mint, .pink, .teal]
    @State private var selection: Color? // 기본값은 선택 없음
    
    var body: some View {
        NavigationSplitView {
            // 사이드바
            List(colors, id: \.self, selection: $selection) { color in
                NavigationLink(color.description, value: color)
            }
        } detail: {
            // 디테일 영역
            if let color = selection {
                ColorDetail(color: color)
            } else {
                Text("Pick a color")
            }
        }
    }
}

동작 방식을 살펴보면

먼저 NavigationLink을 탭하면 selection 상태가 업데이트가 되고, 코드에서 selection을 변경하면 해당 링크가 활성화됩니다.

그리고 양방향 바인딩으로 동기화가 되는 방식입니다.

 

Destination 방식 vs Value 방식

 

Destination 방식

  • 간단한 화면 전환
  • 목적지 뷰가 명확할 때
  • 프로그래밍 제어가 필요 없을 때

Value 방식을 사용할 때

  • 프로그래밍 방식 네비게이션이 필요할 때
  • 네비게이션 상태를 추적하고 싶을 때
  • 딥링크나 상태 복원이 필요할 때
  • 대규모 앱 개발 시
// 비추천: 프로그래밍 제어가 어려움
NavigationLink("상세보기") {
    DetailView()
}

// 추천: 유연하고 관리하기 쉬움
NavigationLink("상세보기", value: item)

 

NavigationStack: 네비게이션의 컨테이너

NavigationStack은 루트 뷰를 표시하고 그 위에 추가 뷰들을 쌓을 수 있게 해주는 컨테이너입니다.

 

애플 공식 문서에 따르면

"Use a navigation stack to present a stack of views over a root view. People can add views to the top of the stack by clicking or tapping a NavigationLink, and remove views using built-in, platform-appropriate controls, like a Back button or a swipe gesture."

 

스택은  항상 가장 최근에 추가되고 아직 제거되지 않은 뷰를 표시하며, 루트 뷰는 제거될 수 없습니다.

NavigationLink와 navigationDestination 연결하기 (공식 패턴)

애플이 권장하는 방식은 데이터 타입을 기반으로 네비게이션을 구성하는 것입니다.

struct Park: Identifiable, Hashable {
    let id = UUID()
    let name: String
}

struct ParksListView: View {
    let parks = [
        Park(name: "Yosemite"),
        Park(name: "Sequoia"),
        Park(name: "Grand Canyon")
    ]
    
    var body: some View {
        NavigationStack {
            List(parks) { park in
                // NavigationLink가 Park 타입의 value를 전달
                NavigationLink(park.name, value: park)
            }
            // navigationDestination이 Park 타입을 받아서 처리
            .navigationDestination(for: Park.self) { park in
                ParkDetails(park: park)
            }
        }
    }
}

 

해당 패턴으로 코드를 작성할 경우 장점은 타입 안전성과 재사용성, 명확한 데이터 흐름과 같은 장점이 있습니다.

기본 사용법(상태 관리 없이)

기본적으로 NavigationStack은 자동으로 상태를 관리합니다. 별도의 설정 없이도 뷰 스택을 추적하고 관리합니다.

NavigationStack {
    List(parks) { park in
        NavigationLink(park.name, value: park)
    }
    .navigationDestination(for: Park.self) { park in
        ParkDetails(park: park)
    }
}

위 코드에서 List는 루트 뷰로 항상 존재합니다. 리스트에서 NavigationLink를 선택하면 ParkDetails 뷰가 스택에 추가되어 리스트를 덮습니다. 뒤로 가기를 하면 디테일 뷰가 제거되고 리스트가 다시 나타납니다. 스택이 비어있고 루트 뷰(리스트)만 보일 때는 시스템이 뒤로 가기 컨트롤을 자동으로 비활성화합니다.

네비게이션 상태 관리하기 (path 사용)

하지만 때로는 코드에서 직접 네비게이션을 제어하고 싶을 때가 있습니다. 이때 path 바인딩을 사용합니다.

struct ParksListView: View {
    // 네비게이션 상태를 추적하는 배열
    @State private var presentedParks: [Park] = []
    
    var body: some View {
        NavigationStack(path: $presentedParks) {
            VStack {
                List(parks) { park in
                    NavigationLink(park.name, value: park)
                }
                
                Button("특정 공원들로 이동") {
                    showParks()
                }
            }
            .navigationDestination(for: Park.self) { park in
                ParkDetails(park: park)
            }
        }
    }
    
    // 프로그래밍 방식으로 스택 구성
    func showParks() {
        presentedParks = [Park(name: "Yosemite"), Park(name: "Sequoia")]
    }
}

빈 배열로 초기화하면 뷰가 없는 스택을 의미합니다.

이제 누군가 공원의 NavigationLink를 탭하면:

  1. 스택이 ParkDetails 뷰를 표시하고
  2. 동시에 presentedParks 배열에 해당 park 데이터를 추가합니다

showParks() 메서드는 스택의 표시를 Sequoia 디테일 뷰로 교체합니다 (배열의 마지막 항목). 거기서 뒤로 가면 Sequoia가 배열에서 제거되고, Yosemite 디테일 뷰가 나타납니다.

 

이런 path 방식을 사용하는 경우는 딥링크 (Deep links), 상태 복원 (State restoration), 기타 프로그래밍 방식 네비게이션에서 사용합니다.

 

여러 종류의 뷰 타입 네비게이션

하나의 스택에서 여러 종류의 뷰를 표시하려면, 여러 개의 navigationDestination 모디파이어를 추가하면 됩니다.

NavigationStack(path: $navigationPath) {
    ContentView()
        .navigationDestination(for: Park.self) { park in
            ParkDetails(park: park)
        }
        .navigationDestination(for: Trail.self) { trail in
            TrailDetails(trail: trail)
        }
        .navigationDestination(for: Campground.self) { campground in
            CampgroundDetails(campground: campground)
        }
}

스택은 데이터 타입을 기반으로 NavigationLink와 navigationDestination을 매칭합니다.

NavigationLink vs NavigationStack 비교

구분 NavigationStack NavigationLink
역할 네비게이션 컨테이너 화면 전환 트리거
위치 최상위 컨테이너 Stack 내부
필수성 Link가 작동하려면 필요 Stack 없이는 작동 X
상태 관리 path로 전체 스택 관리 가능 자체 상태 관리 없음
루트 뷰  항상 존재(제거 불가능) 루트 개념 없음

 


감사합니다.

 

잘못된 내용이 있거나 더 좋은 내용 피드백은 언제나 환영합니다!

궁 금하신 부분은 댓글로 질문 부탁드립니다!

728x90
반응형

'Apple > SwiftUI' 카테고리의 다른 글

[SwiftUI] 앱에 Face ID 잠금 기능 적용하기  (8) 2025.07.08
[SwiftUI] SwiftUI 상태 동기화 트러블슈팅  (1) 2025.06.25
[SwiftUI] CADisplayLink를 활용한 부드러운 ProgressView 애니메이션 구현 트러블슈팅  (6) 2025.06.06
[SwiftUI] NavigationStack 사용 시 화면 전환 안 되는 문제와 title 깨짐 문제 트러블슈팅  (0) 2025.06.03
[SwiftUI] SwiftUI로 카메라 기능 구현하기  (2) 2025.03.01
'Apple/SwiftUI' 카테고리의 다른 글
  • [SwiftUI] 앱에 Face ID 잠금 기능 적용하기
  • [SwiftUI] SwiftUI 상태 동기화 트러블슈팅
  • [SwiftUI] CADisplayLink를 활용한 부드러운 ProgressView 애니메이션 구현 트러블슈팅
  • [SwiftUI] NavigationStack 사용 시 화면 전환 안 되는 문제와 title 깨짐 문제 트러블슈팅
P_Piano
P_Piano
Apple 생태계 개발자가 되기 위한 학습과 경험의 기록
    반응형
    250x250
  • P_Piano
    피피아노의 개발 일지
    P_Piano
  • 전체
    오늘
    어제
    • 분류 전체보기 (218)
      • Apple (136)
        • iOS (23)
        • visionOS (5)
        • Swift (71)
        • UIKit (2)
        • SwiftUI (25)
        • RxSwift (2)
        • Xcode (5)
        • Metal (2)
      • C언어 (5)
      • C++ (8)
      • Dart (1)
      • Python (3)
      • JavaScript (17)
      • Git (1)
      • CS (39)
        • 디자인 패턴 (6)
        • 네트워크 (20)
        • 운영체제 (8)
        • Database (5)
        • 자료구조 (0)
      • IT 지식 (2)
      • IT 뉴스 (4)
      • 출처 표기 (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    함수
    제어문
    네트워크
    클래스
    프로퍼티 래퍼
    디자인패턴
    스위프트
    이니셜라이저
    코딩테스트
    swiftUI
    UIKit
    연산자
    프로세스
    Vision Pro
    배열
    프로그래머스
    비동기
    데이터베이스
    티스토리챌린지
    Initializers
    SWIFT
    운영체제
    ios
    자바스크립트
    오블완
    visionOS
    변수
    Apple
    Xcode
    옵셔널
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.1
P_Piano
[SwiftUI] NavigationLink VS NavigationStack
상단으로

티스토리툴바