SwiftUI View Navigate

글쓴이 연유 날짜

사용자가 어플리케이션을 이용할 때, 간단한 앱은 하나의 화면으로 이루어진 경우도 있겠지만, 대부분의 많은 상용 앱들은 개발자도 파악하기 힘들 정도로 기능을 표현하는 많은 화면을 가지고 있습니다. 이때, 사용자가 기능을 이용하기 위해서 어떤 화면에서 다른 화면으로 넘어갈 때, 우리는 화면을 돌아다닌다(navigate)고 표현합니다.

화면의 이동을 표현하는 방법은 앱을 만들 때 쓰는 각 프레임워크에 따라 여러가지 방법이 존재할 수 있습니다. 그 중, iOS의 UI를 개발하기 위한 프레임워크인 SwiftUI와 UIKit에서는 화면 이동의 주요한 방법 중 하나로, Navigating 계층을 지원하는 NavigationView(deprecated), NavigationStack, NavigationController를 제공하고 있습니다.

UIKit에서 뷰를 보여주는 방법

UIKit의 어떤 뷰에서, 다른 뷰를 보여주기 위한 방법은 크게 3가지 정도로 정리할 수 있습니다. (실제 대상이 viewController이냐, view 이냐는 차이도 있긴 하지만, 이렇게 정리하겠습니다.)

  • addSubview를 통해 뷰를 뷰에 붙이는 방법
  • ViewController의 present 메소드를 이용해서 모달로 보여주는 방법
  • NavigationController를 이용해서 ViewController를 push 하는 방법

이때, 우리가 주목하고자 하는 NavigationController는 다른 방법과는 달리 스택 기반의 Navigating hierachy로 뷰 이동을 관리할 수 있습니다. NavigationController를 이용하면 뷰의 이동이 전, 후 관계에 의해서 계층화되므로 서비스의 일련의 절차를 개발적인 관점, 사용적인 관점에서 단순화할 수 있는 장점이 있습니다.

SwiftUI에서 뷰를 보여주는 방법

SwiftUI도 UIKit과 유사한 방법으로 뷰를 보여줍니다. 다만, SwiftUI에는 View와 ViewController의 구분이 실질적으로 없기 때문에(모든 View가 ViewController로 해석될 여지를 지닙니다.) 모달로 보여주는 방법은 view의 modal presentation 연관 모디파이어로 나타납니다.1https://developer.apple.com/documentation/swiftui/modal-presentations

SwiftUI에서 Navigation hireachy를 지원하는 구조는 iOS 16 이전까지는 NavigationView와 NavigationLink로 표현되었고, iOS 16 이후부터는 NavigationStack, NavigationLink, NavigationSplitView, 그리고 NavigationPath로 표현됩니다. 기존에 NavigationView를 이용해보셨다면, NavigationView와 이동을 담당하는 NavigationLink만으로 충분히 구조가 표현될 수 있는데, 왜 이렇게 복잡해졌는지 의아하실 수도 있을 수 있습니다. 뭐가 좋아진 걸까요?

NavigationView와 NavigationLink은 직관적

NavigationView와 NavigationLink를 이용한 View 이동은 상당히 직관적입니다.

내비게이션 뷰의 이동 방식

NavigationView의 세상에는 NavigationView와 Link만이 존재합니다. 이 간단한 구성은 SwiftUI가 발표된 이래로 3년 동안이나, 작동해왔습니다. 그런데 이상하지 않습니까? SwiftUI의 핵심적인 생각인 데이터를 반영하는 UI가 NavigationView에서 쉽게 적용될 수 있는 걸까요?

View는 Data를 표현해야 한다

Data Flow Through SwiftUI (https://developer.apple.com/videos/play/wwdc2019/226/)

우리가 처음 SwiftUI를 만났을 때, 열광했던 부분 중 하나는, 유저는 데이터를 변경하고, 뷰는 그 데이터를 반영하여 데이터와 뷰에 일그러짐이 없는(DataBinding) 상태가 지속적으로 유지된다는 사실이었습니다. 하지만, 위에서도 보았다시피, 액션에 기반한 NavigationView는 이러한 상태를 표현하기 어려웠습니다. Link를 통해 이동하는 페이지는 대부분의 경우 View 영역에서만 작동이 이루어졌고, NavigationView의 구조를 데이터를 통해 표현하기도 어려웠습니다.

새롭게 도입된 NavigationStack은 navigationDestination(for:destination:) 모디파이어를 통해, 유저의 액션 자체가 아닌, 액션으로 인한 데이터로부터 페이지를 이동할 수 있게 되었습니다.

데이터 구조와 Router 도입의 필요성 (그냥 데이터로 표현되기 때문에 router를 이용한 구조에 특히 적합(router가 데이터만 반영하면됨.)하다고 생각하는데, 클린 스위프트 핸드북 사서 https://clean-swift.com/handbook/ 읽어보고 추가 수정할 예정)

또한, 새롭게 도입된 NavigationPath를 이용해서 NavigationStack의 계층 구조를 데이터로 표현할 수 있습니다. 데이터로 계층 구조가 표현되기 때문에 프로그램 상에서 Navigation의 계층 구조를 임의적으로 수정하는 것도 편해졌고, 더욱이 이러한 계층을 SnapShot으로 저장하기도 편해졌습니다.2https://developer.apple.com/documentation/swiftui/navigationpath?changes=_5

레이아웃 구성에 대한 역할 분담

또 다른 NavigationView의 문제점은 Navigation hirachy로 계층 구조를 표현하는 업무 외에도 레이아웃 업무를 맡고 있었다는 점입니다. 기존 NavigationView를 이용해서 iPad 등에 SplitView를 적용하려면 navigationViewStyle(_:)과 같은 모디파이어를 사용해야 했습니다.

NavigationView 혼자로는 Sidebar와 같은 레이아웃 구성요소를 처리하는데 한계가 있었습니다. 예를 들어서 사이드 바의 항목들과 초기 컨텐츠의 구조화가 어려웠습니다. 아래와 같이 List와 초기 컨텐츠를 같이 View 인자로 보내거나, 다른 방법으로는 초기 컨텐츠를 보내지 않고 사이드바가 보일 때, 코드 상으로 선택해주는 것이었습니다. (만족스럽게 작동하지 않음.)

초기 콘텐츠와 사이드바 요소가 존재하는 예제

이러한 구조를 NavigationSplitView를 도입하면서, sidebar contents 및 레이아웃 구성 책임을 분리할 수 있었습니다.

결론

iOS 16에서 처음 발표된 새로운 Navigating 시스템은 아직 버그도 있지만, 기존 NavigationView가 가지고 있던 한계점을 SwiftUI의 컨셉에 맞게 잘 개선한 버전이라고 생각합니다.

2022.10.23 초안 작성

카테고리: 미분류