The recipe for a great app begins with a clear and robust navigation structure. Join the SwiftUI team in our proverbial coding kitchen and learn how you can cook up a great experience for your app. We'll introduce you to SwiftUI's navigation stack and split view features, show you how you can link to specific areas of your app, and explore how you can quickly and easily restore navigational state.
새로운 Navigation 관련 API는 programmatic navigation and deep linking를 강력하게 지원하며 앱에 완벽한 구조를 만들 조각을 제공한다.
새로운 Navigation API 에 대해 알아보기 전에 기존의 API 를 간단히 확인하고 새로운 API 와 비교해보고자 한다.
📢 The existing APIs are based on links that send views that are shown in other columns or on a stack.
예를 들어, Root View에 Navigation link 들의 리스트가 있다. 이 Link 들 중 하나를 누르게 되면, 이 Link는 Stack 에 View를 Push 하게 된다. 이것은 기본적인 Navigation 에 잘 작동하고 이 패턴을 지속해서 사용할 수 있다.
기존의 Navigation API 의 Root View를 보면 link를 programmatically 하게 보여주기 위하여 Link 에 Binding을 추가한다.
아래의 코드/그림에서 item.showDetail 을 true 로 함으로서 link의 view를 보여줄 수 있다.
새로운 API 에서는, NavigationStack 이라는 전체 container로 모든것을 binding 할 수 있게 된다.
여기서는 Path가 Stack에 푸쉬된 모든 value들을 나타내는 Collection 이다. 새로운 API 에서는 path 를 mutating (변환) 하여 deep link 를 만들 수 있고 혹은 path 의 모든 아이템을 remove 하여 root view 로이동 (pop) 할 수 있다.
두번째로 새로운 containter type 은 NavigationSplitView 이다.
NavigationSplitView 는 아이패드와 맥의 메일, 노트와 같은 multicolumn 앱에 최적화되어 있다. 그리고 NavigationSplitView 는 자동적으로 iPhone에 Single-컬럼 스택으로 iPad 의 Slide Over로 심지어 Apple Watch 와 Apple TV 에도 그에 맞는 적절한 형태로 변환된다.
// OLD
NavigationLink("Show Detail"){
DetailView()
}
// NEW
NavigationLink("Apple Pie", vlaue: applePieRecipe)
이전(위)에는 NavigationLink 가 항상 title 과 나타낼 view 를 포함했다면,
새로 변경된(아래) NavigationLink 에서는 여전히 title 은 포함하지만 보여질 view 를 포함하는 것이 아닌 그 링크가 보여줄 value 를 포함한다. (value-presenting)
앞으로 이 글에서 우리가 보게될 NavigationLink는 스마트해졌다. Link 의 행동은 이것이 속해져있는/나타나는 NavigationStack나 list 에 종속된다.
SwiftUI 에서 새로운 NavigationStack, value-presenting NavigationLinks와 navigationDestinations를 이용하여 pushable 한 stack을 만드는 방법
기존에 NavigationView에 stack 위에 새로운 view를 넣는 navigation 방식에서 NavigationStack 을 사용하는 방식으로 변화
이렇게 작성하는 방식에서
NavigationView { // This is deprecated.
/* content */
}
.navigationViewStyle(.stack)
이렇게 작성하는 방식으로 변화
NavigationStack {
/* content */
}
레시피 시작
이 새로운 API 를 다음의 레시피를 통해서 살펴보자! 예시 앱(레시피)은, 애플워치의 나의찾기 혹은 아이폰의 설정과 같은 view 의 기본 stack 으로 구성된다.
각 섹션에서 recipe 를 선택하여 각 레시피의 디테일한 내용을 볼 수 있고, 디테일한 레시피에서는 Stack에 관련 레시피 중 하나를 쌓아 연관된 레시피를 볼 수 있다. 그리고 여기서 뒤로가기를 통해서, 연관된 레시피에서 오리지널레시피로 그리고 다시 카테고리 리스트로 이동할 수 있다.
🔨 NavigationStack 사용해보기
기본 NavigationStack 안에, category 전체를 도는 List 를 넣고 navigationTitle 을 추가한다.List에서는 각 카테고리별로 별개의 섹션을 갖게 된다. 그리고 각 섹션에서 category 안의 각 레시피를 위해 NavigationLink 을 추가한다. 지금까지 view destination 을 사용하는 기존의 NaviagationLink를 활용해서 RecipeDetail view를 보여주는 link를 만들었다. 지금까지는 이 네비게이션 뷰가 정상적으로 작동한다.
하지만 programmatic 한 Navigation 은 어떠한가? programmatic 한 navigation 을 추가하기 위해서는 이 navigation link를 두 파트( 1. 보여져야하는 value와 이 2. value와 함께 가는 view ) 로 나눠야한다.
한번 알아보자.
먼저 기존의 destination view ( RecipeDetail() ) 를 빼내서, 새로운modifier 인 .navigationDestination() 로 안으로 옮겨주자.
.navigationDestination(for: ) 은 이것이 보여줘야하는 책임이 있는 data의 타입을 선언해준다. ( 여기서는 Recipe 가 된다. )
이 modifier는 recipe value 가 present 될때 stack 에 어떤 view를 push 할지 설명하는 view builder를 가진다.
그리고 새로운 NavigationLink 을 이용하여, recipe value 를 present 하겠다.
🔨 NavigationStack 의 원리
그럼 NavigationStack이 어떻게 이 기능을 수행하는지 차근차근 살펴보도록 하자.
모든 Navigation Stack은 stack 이 보여주는 모든 데이터를 나타내는 path 를 계속 추적한다.
📢 Every navigation stack keeps track of a path that represents all the data that the stack is showing.
path : Stack 이 root view를 보여줄때는 위와 같이 path 는 비어있다.
navigation destinations : Stack 은 또한 그 안에 선언된 모든 navigation destinations 과 stack 안에 쌓인 View 들을 추적한다. 일반적으로 이건은 set 이지만, 여기는 예시라서 하나의 destination 만 가진다. (Recipe) → some View
pushed views : 현재 path 가 비어있기 때문에, 이것도 비어 있게 된다.
아래 예시를 살펴보자 🔬
Apple Pie (value-presenting link)를 클릭하게 되면, path 에 value 가 append 된다.
그리고, navigation stack은 path values 위에 destinations을 매핑하여 stack 에 어떠한 view를 매핑할지 결정하게 된다.
그리고 이 apple pie recipe 안에서, 연계레시피인 Pie Crust 를 탭하게 되면, 이 links는 path에 그 값을 append 하게 된다. NavigationStack 은 마법처럼 또다른 RecipeDetail view (RecipeDetail(Pie Crust))를 Stack 에 쌓게 된다.
path 에 내가 value를 add 할 때마다, NavigationStack 은 다른 view를 push 하게 된다.
뒤로가기 버튼을 누르면 Navigation Stack은 마지막 아이템을 path 에서와 pushed views에서 remove 한다.
그리고 NavigationStack 은 한가지 Trick (기능)을 더 제공한다. 바로 NavigationStack은 이것의 path 를 binding와 connect하는 것을 제공한다.
다시 코드로 돌아가자 🔬
path 를 binding 하기 위해 먼저 State를 추가해보자.
예시에서는 stack에 쌓일 모든 value 가 recipe 이기 때문에 [Recipe] 배열을 사용한다.
[ ] 하지만, 스택에 다양한 데이터 타입을 넣어야한다면 새로나온 type-erasing 한 NavigationPath 컬렉션을 확인하기!
path 상태를 선언했으니, 이제 NavigationStack에 argument를 더해 path를 binding 하여 넘겨주자.
이제 여기서 stack 을 다양하게 활용해보자
다음과 같이 특정한 레시피로 이동할 수 있는 method를 만들 수 있다.
혹은 stack 어디에서든, path를 reet 하여 root 로 바로 이동할 수 있게 만들 수 있다.
이것이 바로 SwiftUI 에서 새로운 NavigationStack, value-presenting NavigationLinks와 navigationDestinations를 이용하여 pushable 한 stack을 만드는 방법이다.
이 레시피는 Mac을 포함한 모든 플랫폼에서 활용가능하며, 특히 iPhone, Apple TV, and Apple Watch 에서 빛날 것이다.
NavigationStack 의 작동 : "Build a productivity app for Apple Watch."
2.2. 두번째 레시피
NavigationSplitView를 사용하여 여러개의 컬럼을 사용하는 Navigation 구현하기
NavigationSplitView 와 value-presenting NavigationLink 와 selection 을 포함한 List를 활용한 multi-column navigation
기존에는 아래와 같이 multi-column navigatoin 을 구현했지만,
// OLD : 2단 컬럼
NavigationView { // This is deprecated.
/* column 1 */
/* column 2 */
}
// OLD : 3단 컬럼
NavigationView { // This is deprecated.
/* column 1 */
/* column 2 */
/* column 3 */
}
Mac 이나 IPad 의 메일에서 찾아볼 수 있는 multicolumn presentaion 을 stack 없이 구현하는 방법을 알아보겠다.
iPad 에서는 sidebar 가 초기에는 hidden 되어있고 버튼을 눌러 이를 나타나게 하고 카테고리를 선택할 수 있다. 그리고 두번째 칼럼에서 레시피를 선택하고 세번째 칼럼은 레시피 디테일을 소개한다.
이 레시피에서는 NavigationSplitView과 새로운 NavigationLink 그리고 List selection 을 이용해보겠다.
이 레시피는 큰 화면 사이즈의 디바이스에서 유용하다. (왜냐하면 modality? 를 피하게 해준다.) 하나하나 들어가서 살펴볼 필요없이 한번에 모든 정보를 확인할 수 있다.
레시피 시작
그럼 자세히 살펴보자.
여기 content와 detail 을 위한 placeholder view 가 잇는 three-column NavigationSplitView 가 있다.
그리고 이 사이드바에 모든 카테고리를 나타내는 List와 navigationTitle 을 추가한다.
리스트 안에는 각 카테고리를 위한 NavigationLink 를 넣어준다.
그리고 어떤 카테고리가 선택되었는지 트래킹하는 @State 를 소개한다. (selectedCategory)
그리고 sidebar 안의 List를 이 selectedCategory 를 사용해서 약간 변경해본다.
우리는 selection 에 대한 binding 을 넘기게 되는 것이다. 이것은 List로 하여금 List 의 컨텐츠가 가 selection 을 다루게 해준다. 무슨말이냐 하면, 일치하는 selection 타입이 있는 list 안에 value를 보여주는 Navigation 링크를 넣게 되면 , 그 링크는 자동적으로 터치되거나 클릭되면 selection 을 자동으로 업데이트한다.
즉, 이제는 sidebar의 카테고리를 선택하게 되면 SwiftUI 는 selectedCategory 를 업데이트 해준다.
selection 과 list 에 대한 자세한 내용이 궁금하다면 : "Organize your interface" by Raj
다음으로 content 컬럼의 placeholder 를 선택된 카테고리를 위한 레시피 리스트로 변경해보겠다. 그리고 이 칼럼에도 navigationTitle 을 달아준다. 선택된 카테고리 (selectedCategory) 처럼 여기에도 같은 테크닉을 이용해서 선택된 레시피를 추적해보자 ( selectedRecipe) . State 를 selectedRecipe 에 사용해주어 레시피 목록에서는 이 상태를 사용하여 각 레시피 선택을 연동해줄 수 있다.
마지막으로 detail 컬럼에서 selectedRecipe 를 보여주기 위한 내용을 업데이트 해준다.
RecipeDetail(recipe: selectedRecipe)
이것을 통해서 우리는 navigation에 full programmatic 한 control 을 또 한번 가능하게 한다.
예를 들어, 오늘의 나의 레시피로 navigate 하기 위해서는 아래와 같이 단순히 selection 상태만 update 해주면 된다!
이것이 바로 SwiftUI 에서 새로운 NavigationSplitView 와 value-presenting NavigationLink 와 selection 을 포함한 List를 활용한 multi-column navigation 을 사용하는 방법이다.
또하나 엄청난 것은 SwiftUI 에서 이와같은 List selection 과 NavigationSplitView 의 조합은 자동적으로 split view를 iPhone 에서 single stack 이나 iPad 에서 Slide Over 로 적용시켜준다는 것이다. 아주 적절하게!
물론 Mac에서는 multicolumn 보기가 적절히 보일 것이며 multiple columns을 보여주지 않는 Apple TV, Apple Watch 등에서도 적절히 single stack 으로 변환되어서 잘 보여줄 것이다.
즉, SwiftUI 에서의 NavigationSplitView 은 모든 플랫폼에서 정상적으로 작동할 것이다!
2.3. 세번째 레시피
다음으로는 2개의 컬럼으로 이루어진 네비게이션을 보자 (iPad 와 Mac의 사진앱에서 볼 수 있는)
카테고리를 선택하면 각 카테고리 안의 레시피가 그리드로 정렬된다. 그리고 각 레시피를 선택하며, detail 영역에 stack 이 push 된다. 그리고 다시 back 버튼을 누르면 그리드 레시피 영역으로 넘어가게 된다.
이것은 navigation split view, stack, link, destination 그리고 list 가 복합된 레시피
첫번째 컬럼은 2번째 예시와 동일하게 구성한다. 차이는 바로 detail 영역에서 에서 나타난다. 새로운 Navigatoin API 는 Composition 에서 큰 장점을 가진다. (자유롭게 구성할 수 있다!)
NavigationSplitView 에서 list를 넣었던것 처럼 detail 영역안에 첫번째 레시피에서 활용한 NavigationStack 을 넣을 수 있다.
이 NavigationStack의 Root View 는 RecipeGrid 이다. RecipeGrid 는 NavigationStack 안에 위치한다. 그 뜻은 stack-related modifier 을 RecipeGrid 안에 넣을 수 있다는 것이다.
RecipeGrid 의 body에서 이것에 대해 자세히 살펴보자
RecipeGrid 는 category 를 parameter로 가지는 view 이다. category 가 optional 이기 때문에 if-let 으로 시작을 할 것이며, else 문에서는 category 를 선택하지 않은 경우 (empty selection ) 에 대한 것을 다룰 것이다.
if 문 안에서, scroll view 와 lazy grid 를 넣는다. lazy grid 레이아웃은 view들을 연속적으로 가지는데 여기서는 ForEach 문을 통해서 recipes들을 iterate 한다. 그리고 각 recipe는 value-presenting 한 NavigationLink 를 가지게 된다. 각 링크는 recipe value를 present하는데, 여기서 link의 closure로 후행되는 label 은 thumbnail와 title을 가지는 RecipeTile이다
이 grid 를 끝내기 위해서 무엇이 남았는가? 지금까지는 NavigatonStack 이 어떻게 recipe에서 detail views 로 map하는지 이야기 하지 않았다.
첫번째 레시피 (NavigationStack 구현) 에서 언급했듯이 새로운 NavigationStack은 navigationDestination modifier 를 vlaue들을 path 를 통해 매핑해서 스택에 어떤 view를 보여줄지 정할때 사용한다.
그럼 이제 navigationDestination modifier 를 더해보자!
근데, 어디에 이 modifer를 추가해야하는가? 에 대해 고민해보자.
아래 이미지와 같이 link 에 직접적으로 modifer를 추가한다면 어떨까?
이것은 2가지 이유로 틀리다고 말할 수 있다.
List, Table 혹은 여기에 있는 LazyVGrid 와 같은 Lazy container 들은 view를 즉각적으로 load 하지 못한다. 따라서 여기에 modifier를 넣게 된다면 destination 은 아마도 load 되지 않을 것이고 이를 둘러싼 NavigationStack 은 아마도 이것을 볼수 없을 것이다.
만약에 modifer를 여기에 더한다면, destination modier는 gird 안에서 모든 아이템 마다 반복할 것이다.
그대 신에 이렇게 ScrollView 바깥에 modifier를 넣어주어야한다. 그이유는 다음과 같다.
ScrollView 바깥에 mofier 를 더하면, scroll position 과 상관없이 NavigatonStack은 navigationDestination 을 볼 수 있다.
또한 modifier를 이곳에 더하게 되더라고 이것은 여전히 이것이 target 으로 하는 link와 close 하기 때문에 좋다.
Navigation destination 은 code를 구성할때 자율성과 make sense하다고 할 수 있다.
다시 NavigationSplitView 으로 돌아와서 programmatic 한 navigation 을 한번 더해보자!
먼저, navigaton path 를 주가할 필요가 있다.
@State 를 더해서 path 를 추가하고 NavigationStack 의 state 와 bind 한다.
여기서 programmatic한 navigatoin 을 이용하기 위해 다음과 같은 코드를 사용해서 showRecipeOfTheDay 와 같은 navigation 경험을 줄 수 있다.
이것이 바로 SwiftUI에서 NavigationSplitView, NavigationStack, value-presenting NavigationLinks 그리고 Lists with selection 을 사용하여 multicolumn navigation 을 사용하는 방법이다.
그리고 이전의 레시피들에서처럼 이것 역시 모든 플랫폼과 자연스럽게, 자동적으로 변환되어 사용할 수 있다.
여기까지 Navigation 관련된 새로 추가된 내용들을 통한 레시피였다. 하지만, dessert 파티 없이 navigation 에 대한 내용은 끝냈다고 할 수 없다!
3. Persistent State
navigation state 를 유지(persist) 하기 위해서 Codable 과 SceneStroage 2개의 개념을 사용하겠다.
세번째 레시피는 3가지의 단계가 있다.
Navigaton Model 타입으로 navigation state 를 캡슐화(encapsulate) 하겠다.
이것은 navigation state를 unit으로서 save 하고 restore 하여 consistent 하게 할 것이다.
그리고 navigation model 을 Codable 하게 만들 것이다.
마지막으로 SceneStorage 를 사용하여 navigation model 을 save 하고 restore 할 것이다 .
programmatic 한 navigation 을 binding을 가지는 link를 이용하여 적용중이라면 아무 강력하게 새로나온 alue-presenting NavigationLink (navigation path 와 list selection 을 가지는) 으로 바꾸기를 권장
The old-style programmatic links are deprecated beginning in iOS 16 and aligned releases.
새로운 API 를 사용할때 Tip
1. `NavigationSplitView`, `NavigationSplitView` 그리고 `List`를 함께 섞어서 사용하기
2. `navigationDestination` modifier들을 사용하여 navigation 어디든지 쉽게 이동하기
3. `NavigationSplitView` 을 사용할 수 있을 때는 이것을 사용하는 것을 추천
NavigationSplitView, NavigationSplitView 그리고 List를 함께 섞어서 사용하기 위해 만들어 진것을 명심할 것
이것들을 자유롭게 섞어서 당신의 앱 사용자들이 좋아할만한 navigation 경험을 만들어 내라
navigation stacks을 사용할때 navigation destinations 은 stack 내부 혹은 이것의 subview이 든 어디든지 될 수 있다.
navigation destinations 는 연관있는 근처에 넣어 유지 보수를 쉽게 해라
단, lazy container 안에 넣지 않도록 할 것
마지막으로 NavigationSplitView 을 사용하는 것이 괜찮은 상황에서는 이것을 통해서 navigation 시작하는 것을 추천한다.
Introduction & Contents
Introduction
관련 영상 : WWDC22 - The SwiftUI cookbook for navigation
Contents
1. New Navigation APIs
1.0 The Existing API
새로운 Navigation API 에 대해 알아보기 전에 기존의 API 를 간단히 확인하고 새로운 API 와 비교해보고자 한다.
예를 들어, Root View에 Navigation link 들의 리스트가 있다. 이 Link 들 중 하나를 누르게 되면, 이 Link는 Stack 에 View를 Push 하게 된다. 이것은 기본적인 Navigation 에 잘 작동하고 이 패턴을 지속해서 사용할 수 있다.
기존의 Navigation API 의 Root View를 보면 link를 programmatically 하게 보여주기 위하여 Link 에 Binding을 추가한다. 아래의 코드/그림에서 item.showDetail 을 true 로 함으로서 link의 view를 보여줄 수 있다.
그러나, 이 코드는 각
link
가 각각의binding
을 필요로 한다.새로운 API 에서는, NavigationStack 이라는 전체 container로 모든것을 binding 할 수 있게 된다.
여기서는 Path가 Stack에 푸쉬된 모든 value들을 나타내는 Collection 이다. 새로운 API 에서는 path 를 mutating (변환) 하여 deep link 를 만들 수 있고 혹은 path 의 모든 아이템을
remove
하여 root view 로이동 (pop
) 할 수 있다.1.1. NavigationStack
첫번째 새로운 container 는 NavigationStack이다. NavigationStack은 Apple Watch 의 나의찾기, iPhone 설정, 새로운 mac OS 인 Ventura 의 시스템세팅 등에서 활용되는 push-pop 인터페이스를 대표한다.
1.2. NavigationSplitView
두번째로 새로운 containter type 은 NavigationSplitView 이다. NavigationSplitView 는 아이패드와 맥의 메일, 노트와 같은 multicolumn 앱에 최적화되어 있다. 그리고 NavigationSplitView 는 자동적으로 iPhone에 Single-컬럼 스택으로 iPad 의 Slide Over로 심지어 Apple Watch 와 Apple TV 에도 그에 맞는 적절한 형태로 변환된다.
1.3. NavigationLink
그리고 NavigationLink 에 강력한 기능들이 추가 되었다.
이전(위)에는
NavigationLink
가 항상 title 과 나타낼 view 를 포함했다면, 새로 변경된(아래)NavigationLink
에서는 여전히 title 은 포함하지만 보여질 view 를 포함하는 것이 아닌 그 링크가 보여줄 value 를 포함한다. (value-presenting)앞으로 이 글에서 우리가 보게될
NavigationLink
는 스마트해졌다.Link
의 행동은 이것이 속해져있는/나타나는NavigationStack
나list
에 종속된다.2. Recipes for navigation
레시피를 들어가기전에, menu 에 뭐가 있는지 살펴보기
아래에 사용될 예시에서는 다음과 같이 구성된 샘플 데이터를 사용한다.
2.1. 첫번째 레시피
기존에 NavigationView에 stack 위에 새로운 view를 넣는 navigation 방식에서 NavigationStack 을 사용하는 방식으로 변화
이렇게 작성하는 방식에서
이렇게 작성하는 방식으로 변화
레시피 시작
이 새로운 API 를 다음의 레시피를 통해서 살펴보자! 예시 앱(레시피)은, 애플워치의 나의찾기 혹은 아이폰의 설정과 같은 view 의 기본 stack 으로 구성된다. 각 섹션에서 recipe 를 선택하여 각 레시피의 디테일한 내용을 볼 수 있고, 디테일한 레시피에서는 Stack에 관련 레시피 중 하나를 쌓아 연관된 레시피를 볼 수 있다. 그리고 여기서 뒤로가기를 통해서, 연관된 레시피에서 오리지널레시피로 그리고 다시 카테고리 리스트로 이동할 수 있다.
🔨 NavigationStack 사용해보기
기본
NavigationStack
안에, category 전체를 도는 List 를 넣고 navigationTitle 을 추가한다.List에서는 각 카테고리별로 별개의 섹션을 갖게 된다. 그리고 각 섹션에서 category 안의 각 레시피를 위해NavigationLink
을 추가한다. 지금까지 view destination 을 사용하는 기존의 NaviagationLink를 활용해서 RecipeDetail view를 보여주는 link를 만들었다. 지금까지는 이 네비게이션 뷰가 정상적으로 작동한다.한번 알아보자.
RecipeDetail()
) 를 빼내서, 새로운modifier 인.navigationDestination()
로 안으로 옮겨주자..navigationDestination(for: )
은 이것이 보여줘야하는 책임이 있는 data의 타입을 선언해준다. ( 여기서는 Recipe 가 된다. )recipe
value 를 present 하겠다.🔨 NavigationStack 의 원리
그럼 NavigationStack이 어떻게 이 기능을 수행하는지 차근차근 살펴보도록 하자. 모든 Navigation Stack은 stack 이 보여주는 모든 데이터를 나타내는 path 를 계속 추적한다.
(Recipe) → some View
아래 예시를 살펴보자 🔬
Apple Pie (value-presenting link)를 클릭하게 되면, path 에 value 가 append 된다. 그리고, navigation stack은 path values 위에 destinations을 매핑하여 stack 에 어떠한 view를 매핑할지 결정하게 된다.
그리고 이 apple pie recipe 안에서, 연계레시피인 Pie Crust 를 탭하게 되면, 이 links는 path에 그 값을 append 하게 된다. NavigationStack 은 마법처럼 또다른 RecipeDetail view (
RecipeDetail(Pie Crust)
)를 Stack 에 쌓게 된다.path
에 내가value
를add
할 때마다,NavigationStack
은 다른view
를push
하게 된다.뒤로가기 버튼을 누르면 Navigation Stack은 마지막 아이템을 path 에서와 pushed views에서 remove 한다.
다시 코드로 돌아가자 🔬
path 를 binding 하기 위해 먼저 State를 추가해보자.
예시에서는 stack에 쌓일 모든 value 가 recipe 이기 때문에 [Recipe] 배열을 사용한다.
path 상태를 선언했으니, 이제 NavigationStack에 argument를 더해 path를 binding 하여 넘겨주자.
이제 여기서 stack 을 다양하게 활용해보자 다음과 같이 특정한 레시피로 이동할 수 있는 method를 만들 수 있다.
혹은 stack 어디에서든, path를 reet 하여 root 로 바로 이동할 수 있게 만들 수 있다.
이것이 바로 SwiftUI 에서 새로운 NavigationStack, value-presenting NavigationLinks와 navigationDestinations를 이용하여 pushable 한 stack을 만드는 방법이다.
이 레시피는 Mac을 포함한 모든 플랫폼에서 활용가능하며, 특히 iPhone, Apple TV, and Apple Watch 에서 빛날 것이다.
2.2. 두번째 레시피
기존에는 아래와 같이 multi-column navigatoin 을 구현했지만,
앞으로는 다음과 같이 구현하면 된다.
Mac 이나 IPad 의 메일에서 찾아볼 수 있는 multicolumn presentaion 을 stack 없이 구현하는 방법을 알아보겠다.
iPad 에서는 sidebar 가 초기에는 hidden 되어있고 버튼을 눌러 이를 나타나게 하고 카테고리를 선택할 수 있다. 그리고 두번째 칼럼에서 레시피를 선택하고 세번째 칼럼은 레시피 디테일을 소개한다.
이 레시피에서는 NavigationSplitView과 새로운 NavigationLink 그리고 List selection 을 이용해보겠다.
이 레시피는 큰 화면 사이즈의 디바이스에서 유용하다. (왜냐하면 modality? 를 피하게 해준다.) 하나하나 들어가서 살펴볼 필요없이 한번에 모든 정보를 확인할 수 있다.
레시피 시작
그럼 자세히 살펴보자.
여기 content와 detail 을 위한 placeholder view 가 잇는 three-column
NavigationSplitView
가 있다.그리고 이 사이드바에 모든 카테고리를 나타내는 List와 navigationTitle 을 추가한다.
리스트 안에는 각 카테고리를 위한 NavigationLink 를 넣어준다.
그리고 어떤 카테고리가 선택되었는지 트래킹하는 @State 를 소개한다. (
selectedCategory
)그리고 sidebar 안의 List를 이
selectedCategory
를 사용해서 약간 변경해본다.우리는 selection 에 대한 binding 을 넘기게 되는 것이다. 이것은 List로 하여금 List 의 컨텐츠가 가 selection 을 다루게 해준다. 무슨말이냐 하면, 일치하는 selection 타입이 있는 list 안에 value를 보여주는 Navigation 링크를 넣게 되면 , 그 링크는 자동적으로 터치되거나 클릭되면 selection 을 자동으로 업데이트한다.
즉, 이제는 sidebar의 카테고리를 선택하게 되면 SwiftUI 는 selectedCategory 를 업데이트 해준다.
다음으로 content 컬럼의 placeholder 를 선택된 카테고리를 위한 레시피 리스트로 변경해보겠다. 그리고 이 칼럼에도 navigationTitle 을 달아준다. 선택된 카테고리 (selectedCategory) 처럼 여기에도 같은 테크닉을 이용해서 선택된 레시피를 추적해보자 ( selectedRecipe) . State 를 selectedRecipe 에 사용해주어 레시피 목록에서는 이 상태를 사용하여 각 레시피 선택을 연동해줄 수 있다.
마지막으로 detail 컬럼에서 selectedRecipe 를 보여주기 위한 내용을 업데이트 해준다.
이것을 통해서 우리는 navigation에 full programmatic 한 control 을 또 한번 가능하게 한다.
예를 들어, 오늘의 나의 레시피로 navigate 하기 위해서는 아래와 같이 단순히 selection 상태만 update 해주면 된다!
이것이 바로 SwiftUI 에서 새로운 NavigationSplitView 와 value-presenting NavigationLink 와 selection 을 포함한 List를 활용한 multi-column navigation 을 사용하는 방법이다.
또하나 엄청난 것은 SwiftUI 에서 이와같은 List selection 과 NavigationSplitView 의 조합은 자동적으로 split view를 iPhone 에서 single stack 이나 iPad 에서 Slide Over 로 적용시켜준다는 것이다. 아주 적절하게!
물론 Mac에서는 multicolumn 보기가 적절히 보일 것이며 multiple columns을 보여주지 않는 Apple TV, Apple Watch 등에서도 적절히 single stack 으로 변환되어서 잘 보여줄 것이다.
즉, SwiftUI 에서의 NavigationSplitView 은 모든 플랫폼에서 정상적으로 작동할 것이다!
2.3. 세번째 레시피
다음으로는 2개의 컬럼으로 이루어진 네비게이션을 보자 (iPad 와 Mac의 사진앱에서 볼 수 있는)
카테고리를 선택하면 각 카테고리 안의 레시피가 그리드로 정렬된다. 그리고 각 레시피를 선택하며, detail 영역에 stack 이 push 된다. 그리고 다시 back 버튼을 누르면 그리드 레시피 영역으로 넘어가게 된다.
이것은 navigation split view, stack, link, destination 그리고 list 가 복합된 레시피
첫번째 컬럼은 2번째 예시와 동일하게 구성한다. 차이는 바로 detail 영역에서 에서 나타난다. 새로운 Navigatoin API 는 Composition 에서 큰 장점을 가진다. (자유롭게 구성할 수 있다!)
NavigationSplitView
에서 list를 넣었던것 처럼 detail 영역안에 첫번째 레시피에서 활용한NavigationStack
을 넣을 수 있다.이
NavigationStack
의 Root View 는 RecipeGrid 이다. RecipeGrid 는NavigationStack
안에 위치한다. 그 뜻은 stack-related modifier 을 RecipeGrid 안에 넣을 수 있다는 것이다.RecipeGrid 의 body에서 이것에 대해 자세히 살펴보자
RecipeGrid 는 category 를 parameter로 가지는 view 이다. category 가 optional 이기 때문에 if-let 으로 시작을 할 것이며, else 문에서는 category 를 선택하지 않은 경우 (empty selection ) 에 대한 것을 다룰 것이다.
if 문 안에서, scroll view 와 lazy grid 를 넣는다. lazy grid 레이아웃은 view들을 연속적으로 가지는데 여기서는 ForEach 문을 통해서 recipes들을 iterate 한다. 그리고 각 recipe는 value-presenting 한 NavigationLink 를 가지게 된다. 각 링크는 recipe value를 present하는데, 여기서 link의 closure로 후행되는 label 은 thumbnail와 title을 가지는
RecipeTile
이다이 grid 를 끝내기 위해서 무엇이 남았는가? 지금까지는 NavigatonStack 이 어떻게 recipe에서 detail views 로 map하는지 이야기 하지 않았다.
첫번째 레시피 (NavigationStack 구현) 에서 언급했듯이 새로운 NavigationStack은 navigationDestination modifier 를 vlaue들을 path 를 통해 매핑해서 스택에 어떤 view를 보여줄지 정할때 사용한다.
그럼 이제 navigationDestination modifier 를 더해보자!
근데, 어디에 이 modifer를 추가해야하는가? 에 대해 고민해보자.
아래 이미지와 같이 link 에 직접적으로 modifer를 추가한다면 어떨까?
이것은 2가지 이유로 틀리다고 말할 수 있다.
그대 신에 이렇게 ScrollView 바깥에 modifier를 넣어주어야한다. 그이유는 다음과 같다.
Navigation destination 은 code를 구성할때 자율성과 make sense하다고 할 수 있다.
다시 NavigationSplitView 으로 돌아와서 programmatic 한 navigation 을 한번 더해보자!
먼저, navigaton path 를 주가할 필요가 있다.
@State 를 더해서 path 를 추가하고 NavigationStack 의 state 와 bind 한다.
여기서 programmatic한 navigatoin 을 이용하기 위해 다음과 같은 코드를 사용해서 showRecipeOfTheDay 와 같은 navigation 경험을 줄 수 있다.
이것이 바로 SwiftUI에서 NavigationSplitView, NavigationStack, value-presenting NavigationLinks 그리고 Lists with selection 을 사용하여 multicolumn navigation 을 사용하는 방법이다.
그리고 이전의 레시피들에서처럼 이것 역시 모든 플랫폼과 자연스럽게, 자동적으로 변환되어 사용할 수 있다.
여기까지 Navigation 관련된 새로 추가된 내용들을 통한 레시피였다. 하지만, dessert 파티 없이 navigation 에 대한 내용은 끝냈다고 할 수 없다!
3. Persistent State
navigation state 를 유지(persist) 하기 위해서
Codable
과SceneStroage
2개의 개념을 사용하겠다.세번째 레시피는 3가지의 단계가 있다.
복잡해보이지만 과정은 간단하다.
상세내용 생략 -> 블로그 원문 참고
Kitchen Tips - 새로운 Navigation 에 대한 Tips
마지막으로 주방(아마도 애플)에서 전달하는 몇가지 Navigation 에 대한 팁이 있다.
기존의 것을 새로운 API로 변경하기
.navigationViewStyle(.stack)
(multicolunm일때)
deprecated
NavigationLink ( ... isActive: ...) 혹은
NavigationLink ( ... tag: ...selection: ...)
사용시 : navigation path 그리고 line selection과 함께 사용
NavigationView
을stack
스타일로 사용하고 있다면,NavigationStack
으로 바꾸기NavigationView
를 사용중이라면NavigationSplitView
으로 바꾸기NavigationLink
(navigation path 와 list selection 을 가지는) 으로 바꾸기를 권장deprecated
beginning in iOS 16 and aligned releases.새로운 API 를 사용할때 Tip
NavigationSplitView
,NavigationSplitView
그리고List
를 함께 섞어서 사용하기 위해 만들어 진것을 명심할 것