SwiftUI를 사용하면서 한번씩 네비게이션 플로우 구현이 UIKit에서 사용했을때보다 다소 불편하고 적용이 어렵다고 느껴지는 경우가 있었다. 이 글을 정리하면서 어떤 방식으로 SwiftUI에서 네비게이션을 적용하면 좋을지 한번 알아보도록 하자
기존 Navigation 방식
이전 방식에서 SwiftUI는 NavigationView, NavigationLink를 사용해 네비게이션을 통한 뷰 이동을 구현했다. 하지만 이 방식에는 몇가지 문제가 있다고 느껴졌는데
먼저 NavigationView 내부에 내장된 네비게이션 상태는 암묵적인 상태로 관리되었기 때문에 프로그램적으로 뒤로가기 , 특정화면으로 이동 등 과 같은 제어가 다소 어려운 측면이 있었다.
예를 들어 동적 컨텐츠를 다루거나 딥링크로 특정 화면으로 이동하려고 할 때 명시적인 상태 조작이 어려워 원하는 흐름을 구현하기 복잡했다.
두번째로 여러단계의 화면 전환이나 동적으로 추가되는 화면에 대해 내부 로직이 불명확해지기 쉬운 상태였다. 리스트 항목을 눌러 여러 상세 화면으로 이동할 때 화면 간 전환 경로를 추적하고 관리하기가 어려운 측면이 존재했다.
마지막 세번째로 NavigationLink를 사용할 때 각 링크에 어떤 데이터가 연결되어 있는지 명확하게 관리하지 않으면, 나중에 잘못된 화면 전환이나 오류가 발생할 가능성이 있었다.
NavigationStack 등장 이유와 개선점
https://developer.apple.com/documentation/swiftui/navigationstack/
이러한 문제를 해결하기 위해 iOS 16부터 NavigationStack이라는 것이 등장하게 되었는데 이 스택이 이전 문제점을 완벽하게 해결했다고는 할 순 없지만 그래도 이전 문제점을 어느정도 상쇄시킬 수 있는 역할을 할 수 있게 된 것 같다.
NavigationStack은 NavigationPath라는 상태 변수를 사용해 현재 스택(화면 전환 경로)을 명시적으로 관리한다. 프로그램적으로 화면을 추가하거나 제거할 수 있어 딥링크나 네비게이션 로직 구현이 조금 쉬워졌다.
NavigationStack은 스택에 저장하는 각 요소에 대해 타입을 명시할 수 있어 enum이나 struct를 사용해 어떤 화면이 언제 나타나야 하는지 정의할 수 있다. 화면 전환의 오류를 방지하고 컴파일 타임에 오류를 잡을 수 있어 안정성이 높아지는 측면이 있다.
또한, 버튼 클릭이나 특정 이벤트에 따라 navigationPath 배열을 조작하면 네이게이션 흐름을 코드로 직접 제어할 수 있다는게 큰 특징이다. 사용자 액션 외에도 다양한 조건( 로그인 후 특정 화면으로 이동 등 )에서 네비게이션을 쉽게 구현할 수 있게 된다.
NavigationStack의 내부 동작 방식과 로직
NavigationStack은 전통적인 스택처럼 마지막에 들어간 것이 가장 먼저 나오게 작동된다.
상태변수인 NavigationPath를 활용하는 방식인데 예를들어
@State private var navigationPath = NavigationPath()
이와 같이 선언된 변수는 현재 네비게이션 스택의 상태를 나타낸다. 이 변수는 배열처럼 동작하며, 각 요소는 Hashble 프로토콜을 준수해야한다.
스택에 새로운 요소를 추가하면(예: navigationPath.append(Screen.detail)), SwiftUI는 해당 요소에 대응하는 화면을 생성하게 되고 스택에서 해당 요소를 제거하면 화면이 사라지게 되는 방식이다.
navigationDestination(for: Screen.self) 라는 클로저를 통해 스택에 들어있는 각 요소가 어떤 뷰와 연결되어 있는지 정의한다. SwiftUI는 스택에 있는 각 타입별 값을 확인하고, 매핑된 뷰를 자동으로 렌더링한다.
예시
// 네비게이션 경로에 넣을 데이터 타입 (타입 안전성을 위해 enum 사용)
enum Screen: Hashable {
case detail
case second
}
struct ContentView: View {
// NavigationPath를 이용해 현재 네비게이션 상태를 관리
@State private var navigationPath = NavigationPath()
var body: some View {
NavigationStack(path: $navigationPath) {
VStack(spacing: 20) {
// NavigationLink를 통한 기본 네비게이션
NavigationLink("Go to Detail", value: Screen.detail)
NavigationLink("Go to Second", value: Screen.second)
// 버튼을 통해 프로그램적으로 네비게이션 스택에 값을 추가
Button("Programmatically Navigate to Detail") {
navigationPath.append(Screen.detail)
}
}
.navigationTitle("Home")
// 스택에 있는 요소에 따라 어떤 화면을 보여줄지 정의
.navigationDestination(for: Screen.self) { screen in
switch screen {
case .detail:
DetailView()
case .second:
SecondView()
}
}
}
}
}
struct DetailView: View {
var body: some View {
VStack {
Text("Detail View")
.font(.largeTitle)
}
.navigationTitle("Detail")
}
}
struct SecondView: View {
var body: some View {
VStack {
Text("Second View")
.font(.largeTitle)
}
.navigationTitle("Second")
}
}
NavigationStack은 전체 네비게이션 계층을 감싸고 $navigationPath 바인딩을 통해 현재 스택 상태를 관리한다. navigationPath는 스택처럼 동작하기 때문에 요소를 추가하면 새로운 화면이 푸시되고, 제거하면 뒤로 가게 되는 형식이다.
Screen이라는 enum을 사용해 각 화면을 타입 안전한 상태로 구분하고 navigationPath.append(Screen.detail)을 호출해 코드로 직접 네비게이션 스택을 변경한다.
이로 인해 사용자 인터랙션 뿐만 아니라 다양한 이벤트에 따라 네비게이션 흐름을 제어할 수 있다.
기존의 NavigationView와 NavigationLink 방식은 간단하게 사용할 순 있었지만 네비게이션 상태의 명시적 관리, 동적 경로 처리 등 제어가 어려운 측면이 있었다. NavigationStack은 이러한 문제점을 해결할 수 있었고 명시적인 NavigationPath를 통해 상태를 관리하고, 타입 안전한 데이터 기반의 전환 로직을 제공하면서 복잡한 네비게이션 흐름을 보다 쉽게 구현할 수 있도록 개선된 것 같다.
오늘은 이렇게 네비게이션에 대해서 알아봤는데 이 내용을 토대로 실제 지금 하고 있는 방식에 적용해봐야겠다 🙂
'◽️ Programming > SwiftUI' 카테고리의 다른 글
SwiftUI를 활용한 Pagination 구현하기 (1) | 2025.01.04 |
---|---|
SwiftUI에서 Charts를 구현해보자 (1) | 2024.11.18 |
SwiftUI와 UIKit의 다른 생명주기 방식 (0) | 2024.10.22 |
[SwiftUI] onChange 역할을 알아보자 (0) | 2024.08.09 |
[SwiftUI] @StateObject , @ObservedObject, @EnviromentObject의 역할과 차이점을 자세하게 알아보자 (1) | 2024.07.23 |