본문 바로가기
iOS/SwiftUI

#11 Lists & Navigation

by HaningYa 2020. 10. 22.
728x90

single view 인 앱은 거의 없다.
대부분의 앱들은 여러개의 view 를 가지고 그들 사이를 부드럽게 navigate 할 수 있는 방법을 제공해야 한다.

navigation 을 설계할 때는 여러 요구 사항의 균형을 맞춰야 한다.
데이터를 사용자에게 논리적으로 제공해야 하며 특정 작업을 수행하는 방법을 쉽게 파악할 수 있도록 해야한다.

SwiftUI는 데이터를 보여주면서 navigation을 관리할 수 있는 통일화된 interface를 제공한다.
이번 챕터에서는 view 들 간의 여러 type 의 navigation을 쌓으면서 어떻게 데이터를 사용자에게 나타내는지에 대해서 배우게 될 것 이다.

Getting started

starter 프로젝트를 열면 공항을 위한 앱의 초기버전이 들어있다.
이번 챕터에서는 오늘 출발하고 도착하는 비행편을 표시하는 앱을 만들 것 이다.
실제로는 이러한 정보들은 Combine 을 통해 API 에서 받아오게 된다.
이번 앱에서는 더미 데이터를 사용한다.

먼저 Models 폴더에 있는 파일을 살펴보면

  • FlightInformation.swift 는 비행에 관한 정보를 담고 있다.
    generateFlight() 라는 static 함수를 가지고 있어서 비행 한편에 대한 테스트 데이터를 생성한다.
    generateFlights()는 30개의 비행편의 배열을 생성한다.
  • ContentView.swift 는 flightInfo 변수를 가지고 있다. (앱이 실행될 때 마다 새로운 비행편을 가지고 있다.)

Navigating through a SwiftUI app

SwiftUI 앱의 navigation 을 설계할 때 직관적으로 작업을 수행할 수 있도록 navigation pattern을 제공해야 한다.
navigation 이 잘된 것에 대해서는 거의 알아채지 못하지만 navigation 이 안좋을 경우 사용자는 참지않고 앱을 삭제한다.
SwiftUI 는 cross-platform framework 이지만 주로 iOS 와 iPadOS 에 영향을 많이 받았다.
결과적으로 SwiftUI는 iOS 와 iPadOS에서 자주 볼 수 있는 디자인 가이드라인과 pattern에 결합되어 있다.

SwiftUI navigation은 2가지 스타일로 정리된다.

  • flat
  • hierarchical

SwiftUI에서는 TabView를 통해 flat 계층을 구성한다.
flat navigation 구조는 카테고리별로 나눠진 컨텐츠에 따라 화면을 이동할 때 적합하다.
flat pattern 의 layout은 넓으며 많은 top-level view 가 존재한다. 
각각의 view 는 얕은 depth 를 가진다. 
이러한 navigation 구조는 유저가 어떠한 view 로 향하는 경로를 쉽고 짧게 만들어 준다.
너무 많은 카테고리들은 좋지않다.

from SwiftUI by tutorials 

Hierarchical navigation은 사용자에게 top 에서는 적은 선택권을 주고 아래로 깊은 구조를 가진다.
SwiftUI에서 NavigationView 를 통해 구현하게 된다.
사용자는 특정 view 로 이동하기 위해 몇 개의 계층을 다시 올라가(backtrack) 해야 할 수도 있다.
계층형 구조는 사용자가 view stack 사이를 적게 바꾸거나 level 별로 더 구체적인 정보를 표현하고 싶을 때 적합하다.

from SwiftUI by tutorials 

view나 view stack 을 위한 레이아웃은 대부분 위 두가지 방법을 합친 구조일 것이다. 
top-level 에서는 TabView 를 사용하고 각각의 view 는 NavigationView를 통해 뎁스를 늘려갈 수 있을 것이다.
navigation 구조가 어떻던 중요한 점은 일관성 (consistent) 를 가져야 한다는 것이다.
서로 다른 navigation 패러다임을 바꿔가며 사용하는 것은 사용자에게 혼란을 줄 수 있기 때문이다.

Creating navigation views

ContentView.swift에서 Tabbar를 만든다.

  1. TabView control 로 tabview 를 만든다.
  2. TabView의 enclosure에 각각의 tab이 되는 view 를 제공하고 modifier를 통해 tab에 대한 정보를 제공한다.
  3. tabItem(_:) 메소드를 통해 tab 마다 image, text를 넣어준다. 
  4. 각각의 tab는 system image 와 text label 을 표시한다. tab 에는 Text, Image 또는 Image+Text를 사용할 수 있다.

view가 동일하기 때문에 (FlightBoard) 따로 board 이름을 줄 수 있는 변수를 FlightBoard.swift 에 추가한다.

그리고 ContentView 에도 파라미터를 넣어준다.

Using navigation views

navigation view 는 view 를 바꿀 때 stack 을 이용한다. 
각각의 view 에서 사용자는 stack 에 있는 새로운 view로 진행할지 판단한다.
stack 에서 뒤로는 갈 수 있지만 여러 view 를 건너뛰어서 이동할 순 없다.

화면이 큰 디바이스 같은 경우 SwiftUI는 split-view 인터페이스를 제공한다.
한쪽 view 는 정적으로 남아있고 다른 view 가 사용자의 navigation 에 따라 view stack 을 이동하게 된다
(아이패드 설정앱)

NavigationView 를 이용해서 view 탐색 계층을 바꿔볼 것 이다.
Home 에서 두개의 비행 편성표로 넘어가는 링크 버튼을 만들 것이다.
ContentView에서 Tabbar 을 삭제하고 NavigationLink 를 만들어 보자

  • NavigationView 는 view stack 의 시작점을 나타낸다. 
    master-detail flow 로 데이터를 다루게 될 것이다.
    사용자에게 두개의 option 을 제공해서 선택하게 만든다.
    navigation은 toolbar와 view 를 빠져나오는 Link 도 제공한다.
  • NavigationLink 구조체는 button 을 만들어 사용자가 stack 의 더 깊은 view 로 갈 수 있게 해준다
    destination 파라미터는 사용자가 버튼을 눌렀을 때 보여질 view를 담게 된다.
  • NavigationLink 의 enclosure는 link를 보여주는 view 가 된다. 이번 경우는 text 를 보여주고 있다.
  • navigationBarTitle 메서드를 통해 navigationView 상단에 보여질 title 을 결정한다.

navigationBarTitle을 NavigationView 가 아닌 ZStack 에 modifier 로 넣은게 이상하게 생각될 것 이다.
하지만 view 계층을 정의하고 있기에 view title은 view stack 이 이동될 때 변하게 된다.이 메서드는이 컨트롤이있는 navigation view를 찾아 그에 따라 제목을 변경한다.
현재 navigation view를 변경하는 모든 메서드는 스택 내의 view에서 작동한다. (stack 에서가 아닌)
결국 이런 setting 들은 preview 나 live view 에서 볼 수 없다는 뜻 이다.

iPhone 이나 Apple TV SwiftUI 는 기본적으로 navigation stack 을 사용한다.
더큰 iphone, ipad, mac 의 경우 apple 은 기본적으로 split-view 스타일의 navigation을 제공한다.
한가지 단점이라면 큰 화면에서 양쪽 끝으로 swipe 해야한다는 것이다.

이런 default behavior 도 navigationViewStyle을 통해 override 할 수 있다.
그래서 각 플랫폼 별로 default 를 정해주려면 .navigationViewStyle(StackNavigationViewStyle())를 NavigationView 의 아래에 추가해 주면 된다.

다음으로 공항으로부터 도착하고 출발하는 항공편을 보여주는 view를 구현해야 한다.
그리고 SwiftUI 에서 데이터를 다루는 것도 해볼 것 이다.

Displaying a list of data

FlightBoard.swift를 열어보면 기본 뼈대만 잡혀 있다.
어떤 argument 가 오는지에 따라서 출발 또는 도착 하는 항공편의 정보를 보여줄 것 이다.

보여줄 배열을 선언하고 preview_provider 에 데이터를 공급해준다.

FlightBoard를 사용하는 ContentView 에도 데이터를 공급한다.

 

SwiftUI는 플랫폼 마다 배열로된 데이터를 처리하는 방법을 제공한다. 

첫번째는 ForEach 이다.

ForEach는 전달 된 데이터의 항목을 반복하여 각 요소에 대한 클로저를 호출하고 현재 요소를 전달하고 클로저에서 요소에 대해 표시 할 뷰를 정의한다.

id : 매개 변수는 SwiftUI가 ForEach에 전달 된 데이터에 대한 기대치를 가지고 있음을 암시합니다??

Making your data more compatible with iteration

ForEach 로 전달되는 데이터는 각각의 배열 요소를 구분할 수 있게 방법을 제공해야 한다.
이 반복문에서 id: 파라미터를 통해 SwiftUI가 \.id 프로퍼티를 배열의 요소를 구분하는 용도로 사용하라고 말한 것이다.
이런 unique identifier를 만들기 위해 팔요한 것은 Hashable protocol 만 지켜주면 된다.  (이미 Swift 의 String 와 Int 가 가지고 있음)
또는 Foundation 의 UUID 나 URL type을 사용해도 된다. 

Identifiable protocol 을 준수하면 unique identifier를 명시하지 않아도 된다. 이 프로토콜은 Swif 5.1에서 새로나왔으며 SwiftUI가 데이터의 고유 식별자를 결정하는 방법을 알고있는 정의 된 메커니즘을 제공한다. 이 프로토콜은 id 라는 이름을 가진 Hashable protocol 을 준수하는 프로퍼티만 있으면 된다.

FlightInformation class 에는 id 라는 프로퍼티가 있기때문에 단순히 SwiftUI 에게 알려만 주면 된다.

FlightInformation.swift 에서 FlightInformation 이 Identifiable 을 준수하도록 한다.

 

이렇게 되면 FlightBoard 에서 ForEach 를 쓸때 id 를 지우고 쓸 수 있다.

이렇게 데이터가 데이터가 많아지면 한 화면에 담기 어려워진다.
그래서 ScrollView 에 대해서 배워보자 

 

Showing scrolling data

FlightBoard.swift 를 열어 body를 수정한다.

preview 를 보면 데이터를 전부 표시할 공간이 부족한 걸 볼 수 있다.

view 에 전부 담기 힘든 큰 크기의 데이터를 다루는 몇가지 방법이 있는데 첫번째 방법이 ScrollView 로 데이터를 wrap 하는 것 이다.
ForEach 를 ScrollView 로 감싸라

 

ScrollView는 view 를 scrollable content region 내에 담는다. 이 region은 다른 view 에 영향 없이 데이터를 scroll 할 수 있게 한다.

SwiftUI 는 VStack 에 view 를 담았기 때문에 자동으로 수직 스크롤로 판단한다.
이 뜻은 만약 view 의 문자 길이가 길어져 width 가 화면을 벗어날 때  SwiftUI 는 자동으로 수평 스크롤을 적용하지 않는다는 것 이다.

양 방향으로 스크롤을 하고 싶으면 아래와 같이 override 하면 된다.

ScrollView([.horizontal, .vertical]) {

ScrollView 는 복잡한 view 보단 general 한 view 를 만드는데 적합하다.

single column list of data 를 표현하는 또다른 방법으로 List 가 있다.

Creating Lists

ForEach 는 배열의 요소를 순회하지만 데이터에 대해 뭘 할지는 개발자에게 맡긴다.
배열을 순회해서 데이터를 보여주는건 빈번한 작업이기 때문에 모든 플랫폼들은 이 작업을 위한 control 이 만들어져 있다.
SwiftUI는 List struct를 제공한다.

FlightBoard.swift에서 List를 써보자

List는 플랫폼 빌트인 list format 을 사용해 많은 작업 없이 기능을 구현하게 도와준다. 
UITableView 보다 훨씬 구현하기가 쉽다.

ForEach 는 거의 모든 데이터의 집합에 대해 순회할 수 있고 각각의 요소에 대한 view 를 만든다.
List는 ForEach 에서 특정 케이스인 one-column-data 에 대해 나타낼 때 사용한다.
거의 모든 framework 와 platform 은 이런 비슷한 control 을 제공한다.
만약 data 모음에 좀더 유연하게 작업하고 싶다면 ForEach를 사용해라

user 가 비행편을 리스트에서 선택했을 때 더 많은 정보를 새로운 view 에 표시하는 부분을 구현해본다.

List를 NavigationView 로 감싼다.

감싸게 되면 문제가 생기는데 ContentView 에서 이미 NavigationView 를 추가했기 때문에 navigation stack 이 이미 있기 때문이다. 

두개의 backlink 를 가지는 건 navigation view 의 컨셉을 무너뜨린다.
navigation view 는 초기 view 로부터 시작되는 stack 을 만들 기 때문에 항상 하나의 NavigationView 를 계층에서 가지고 있어야 한다. 

navigation을 구현하기 전, 먼저 flight board 에 정보를 하나 추가한다.

 

비행편 리스트의 item view 를 더 바꿔보자

그리고 FlightBoard 에서 Text 대신 방금 만든 FlightRow view 로 바꿔준다.

Adding navigation links

FlightBoardInformation.swift 를 만들어서 List row 를 눌렀을 때 이동되는 view 를 만들어 보자

그리고 FlightBoard에서 List 의 NavigationLink 를 FlightBoardInformation으로 정해준다.

 

Adding items to the navigation bar

Flightboard에서 취소된 항공편을 가리는 navigation bar Toggle 을 만들어 본다.

Key points

  • app navigation 대부분 수평과 수직의 flow 조합으로 이루어 진다.
  • Tab view 는 flat navigation을 보여주고 빠르게 view 사이를 바꿀 수 있게 한다.
  • Navigation view 는 view stack을 만들어 더 들어가거나 나올 수 있다.
  • navigation link 는 view 와 stack 의 다음 view 를 연결한다.
  • 단 하나의 NavigationView 를 view stack 에서 가지고 있어야 한다.
  • stack 을 관리하기 위해 변경사항을 navigation view stack 에 적용한다. (NavigationView 자체가 아닌)
  • ScrollView 는 영역을 감싸서 다른 뷰에 영향을 안주고 스크롤 할 수 있게 한다.
  • SwiftUI는 데이터 순회방식으로 2가지를 제시한다. ForEach
  • List 배워봤다.
  • ForEach 와 List 사용하는 데이터는 꼭 uniquely identify되어야 한다.
    Hashable protocol 을 가지는 attribute를 명시하거나 Identifiable protocol 을 준수하거나

링크

iOS navigaion HIG

https://developer.apple.com/design/human-interface-guidelines/ios/app-architecture/navigation/

watchOS

https://developer.apple.com/design/human-interface-guidelines/ watchos/app-architecture/navigation/

tvOS

https://developer.apple.com/design/human-interface-guidelines/tvos/app- architecture/navigation/

WWDC2019 Apple guideline about navigation list

https://developer.apple.com/videos/play/wwdc2019/216/

728x90

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

#13 Drawing & Custom Graphics  (0) 2020.10.23
#12 Conditional Views  (0) 2020.10.23
#10 Gestures  (0) 2020.10.22
#9 State & Data Flow  (0) 2020.10.22
#8 Introducing Stacks & Containers  (0) 2020.10.22

댓글