Search

[WWDC] The craft of SwiftUI API design: Progressive disclosure

들어가며

SwiftUI팀은 SwiftUI를 설계할 때 명확하게 정의된 원칙을 바탕으로 결정을 내리려고 노력했는데, 그 원칙 중 하나인 “Progressive Disclosure” 에 대해서 다루도록 하겠습니다.
 : 다시 사용할 수 있는 Component나 추상화 작업을 하는 순간 여러분도 API 디자이너 랍니다.
이번 세션에서는 SwiftUI팀의 설계 과정이 어떻게 진행되는지 보여주고 “Progressive Disclosure”에 관해 배운 걸 공유합니다. 다음에 재사용 요소나 추상화 작업을 할 시에 새로운 도구로 활용할 수 있게 말이죵.

Progressive Disclosure

Progressive Disclosure는 API 설계에서만 사용하는 용어가 아니라고 해요. 일반적인 macOS UI에서도 볼 수 있는 개념입니다.
예를 들어서, macOS에 있는 저장하기 창을 볼게요.
처음 저장하기 창이 나타날 때는 기본 저장 위치가 설정되어 있습니다.
창의 드롭다운 메뉴에는 주로 사용하는 위치가 있어서 선택 가능성이 큰 위치에 쉽게 접근이 가능합니다.
원하는 경로를 찾기 위해서 파일 시스템을 검색하려고 창을 더 확대하면 위의 UI가 나타납니다.
필요할 때마다 단계적으로 더 복잡한 UI가 드러나는 걸 볼 수 있어요.
이런 경험을 API에서도 제공하고자 해요. 멋진 UI 경험을 코드에서 제공하려면 API를 사용하는 느낌이 좋아야 합니다.
개발자들은 코드를 “Declaration site”의 입장에서 코드를 바라봐요. 하지만 코드 사용의 느낌을 좋게 하려면 다른 관점에서 봐야 합니다.
코드가 실제로 사용되는 곳, “Call Site”에서 봐야 합니다.
API 설계에서는 Use case의 복잡도에 따라 Call Site의 복잡도가 증가하며 “Progressive Disclosure”를 적용합니다.
  이상적인 API는 간단하고 접근성이 좋지만 강력한 Use case도 수용해야 합니다.
이는 개발자들에게 큰 이점을 줘요!
첫 빌드와 실행에 걸리는 시간을 최소화하여 API를 빠르게 사용할 수 있어요.
learning curve가 낮아서 코드에 쉽게 익숙해질 수 있어요.
피드백 주기가 깔끔해집니다. Progressive Disclosure를 채택하는 API는 각 단계에 만들 걸 보고 한 부분씩 추가가 가능해져요.
  빠른 주기로 앱을 개선하여 개발할 수 있고 처음부터 큰 노력을 투입할 필요가 없어요.
 : 해당 원칙을 특정 API 설계에 어떻게 적용할까요?
1.
Consider common use cases : 기능을 Progressive disclosure하기 위해 단순한 케이스의 형태 파악
2.
Provide intelligent defaults : 필요할 때만 일반적인 사례를 구체화
3.
Optimize the call site : call site의 모든 글자에 목적을 부여
4.
Compose, don’t enumerate : 열거하는 대신 각 조각을 구성

Consider common use cases

Consider common use cases의 예를 볼게요. SwiftUI의 Label이 이 부분을 잘 담고 있어요.
버튼을 만들면 우리는 버튼의 레이블을 만들라는 요구를 받습니다. 보통의 버튼은 텍스트를 하단의 이미지처럼 SwiftUI로 쉽게 작성할 수가 있죠.
하지만 버튼을 커스텀화하고 싶다면 SwiftUI의 다른 override 메소드를 사용해서 레이블 형태의 임의의 뷰로 나타낼 수 있어요. 그 때는 간단한 컨트롤에도 복잡한 기능을 넣을 수 있게 되죠.
대부분(99%)은 간단한 버전만 필요하긴 합니다.

Provide intelligent defaults

Common use case 사례를 간결하게 만들기 위해 명시적으로 구체화하지 않는 Intelligent defaults를 제공합니다. SwiftUI 전체에서 가장 많이 사용되는 API를 발견할 수 있을 겁니다.
텍스트!
Text 는 기본값을 나타내기에 좋은 예시죠. 이 코드들을 작성하는 동안 따로 지정해야 하는 것들에 대해서 따로 생각할 필요도 없었을 겁니다
SwiftUI는 환경 Local의 앱 번들 안에 있는 Localization 스트링을 검색해서 텍스트를 현지화 시켜줘요.
현재 설정한 색상에 자동으로 맞춰서 다크 모드를 바로 지원해주기도 합니다.
Dynamic type를 사용하는 경우에는 크기 설정에 따라 글자 크기가 자동으로 커지거나 작아집니다.
2개의 텍스트를 위아래로 배치한 경우 텍스트 사이의 간격을 현재 텍스트의 맥락에 맞게 자동으로 조정해요.
모든 동작은 수동으로 지정할 수 있지만 SwiftUI의 Intelligent default 덕분에 common use case와 관련 없는 내용은 call site에 나타나지 않아요.
  텍스트는 API 중에서도 굉장히 미니멀한 예시지만 Intelligent default는 모든 call site에 적용됩니다.
Toolbar를 예시로 들어볼게요.
버튼이 여러 개 있는 Toolbar가 있습니다. 각 버튼의 위치를 명시적으로 지정하지 않아도 버튼이 플랫폼에 맞게 배치될 겁니다.
macOS에서는 Toolbar가 왼쪽에 나타나지만 iOS에서는 내비게이션 바에 나타납니다. watchOS에서는 첫 번째 항목만이 내비게이션 바 아래 고정돼어 있어요.
원한다면 언제든지 커스텀할 수 있지만 Intelligent default가 대개의 사례를 처리해줘요.

Optimize the call site

Common Use case를 고려하고 Intelligent default를 제공하면 멋진 경험을 만들 수 있지만 API가 무겁게 느껴지거나 정제되지 않으면 경험을 망칠 수 있어요. 그래서 나온 마지막 전략이 Call site 최적화입니다.
Table를 살펴볼게요.
열이 여러 개인 표는 기능이 설정할 게 많고 기능도 많아요. 하지만 대부분은 훨씬 간단하게 사용하고 다양한 기능이 필요 없어요.
Common usecase에도 훌륭한 경험을 제공하고 싶어요. 그러면 간단한 표에 관한 코드를 보면서 어떻게 최적화할 지 알아봅시다. 표는 각 행에 데이터를 생성하는 방법을 명시해요.
이 표는 현재 읽는 책을 열거하고 있어요. 각 책에 대한 행도 만들고 있습니다. 다음에는 각 행의 데이터를 열에 배치하는 방식을 명시합니다. 표에는 제목 열과 작가 열이 있네요.
또한 정렬 순서를 지정하여 사용자가 열의 제목을 클릭할 때 정렬이 변경되도록 했죠.
마지막으로 정렬 순서가 바뀌었을 때 표를 재정렬하는 코드를 추가했습니다 정보가 많으니까 일단 살펴보면서 Progressive disclosure를 적용하여 call site를 최적화해 보죠
가장 일반적인 활용 사례는 행과 관련이 있습니다. 대개는 행 영역이 이 예시와 비슷할 거예요 컬렉션에 ForEach 문을 돌려 각 항목에 행을 제공하죠
하지만 개발자가 이 과정을 루핑할 필요가 없습니다.
컬렉션을 표에 직접 통과시켜 ForEach 동작은 배경에서 수행되도록 하여 Call site를 단순화했습니다. 하지만 더 단순화할 수 있어요.
대부분의 표에 나타내려는 값이 String이면 Text를 이용하여 열에 표시합니다. 케이스에 맞게 Call site를 최적화했죠.
값의 키패스가 스트링을 지정할 때 TableColumn과 관련된 뷰를 삭제할 수 있습니다.
더 최적화해볼게요.
Call site에 있는 정보 중에서 모든 표에 필요하지 않은 정렬 기능이 있습니다. 가장 단순한 활용 사례는 정렬에는 관심이 없어요.
즉, 이게 최종 코드겠군요.!
Call site의 모든 글자가 명확한 목적을 수행하는데 Processive하게 중요한 질문 2개를 던졌기 때문입니다.
API에 주는 영향을 제대로 고려하지 않으면 길을 잃을 수도 있겠죠?

Compose, don’t enumerate

Stacks, 특히 HStack를 사용해서 설명해볼게요.
일단 HStack에서 가장 핵심 정보가 무엇인지 생각해봅시다. HStack의 콘텐츠를 지정하는 뷰 빌더가 있으니 정렬에 초점을 맞춰서 진행할게요.
왼쪽에 상자를 배치하는 것이 가장 일반적이겠죠. 중앙에 배치하는 것도 일반적인 케이스입니다 요소를 오른쪽에 맞춰 정렬할 수도 있죠.
VStack에는 이미 정렬 관련 API가 있는데 enum을 사용하여 요소로 사용합니다. 우리가 언급한 모든 케이스를 지원하죠.
만약 우리가 요소의 간격을 일정하게 만들고 싶거나 요소 사이에만 간격을 두거나 마지막 요소 전에만 간격을 두고 싶으면 어떻게 해야할까요?
이렇게 항목이 많아질겁니다.
이런 구조를 유지하기도 힘들고, 원하는 모든 케이스를 enum에 추가해야 하는데 유용한 케이스를 생각해내지 못할 수도 있어요.
일반적인 케이스만 열거하고 편의성을 제공하지 않는다면 API를 구성할 수 있는 조각으로 나눠서 해결책을 만들어보세요!
열거 대신 구성하세요.
Stack의 경우에는 SwiftUI는 Spacer를 제공하여 우리가 열거했던 모든 정렬 방식은 물론 훨씬 많은 정렬을 구성할 수 있게 되었습니다.
“Processive disclosure”를 통해 Call site를 최소화하는 것뿐만 아니라 Call site에서 모든 케이스를 다룰 수 있도록 신중하게 고려했고 Compose를 통해서 해결책을 찾았어요!
“Processive disclosure”를 적용하면 Common use case에서 시간을 아껴줌
Intelligent default로 Common use case에 관한 세부 사항을 고려하지 않아도 됨
Call site를 최적화하여 반복 작업을 빠르게 수행
Compose를 통해 모든 활용 사례를 처리하는 유연한 API 만들기 가능
여러분도 API 설계자이므로 이러한 내용을 작성하는 코드에 적용할 수 있죠. 다른 사람을 위해 설계할 수도 있고 개인 용도일 수도 있습니다.