스트리밍은 대부분의 브라우저와
Developer 앱에서 사용할 수 있습니다.
-
데스크탑급 iPad 앱 빌드
데스크탑급 기능을 활용할 수 있는 iPad 앱을 만드는 방법을 확인하세요. UIKit 팀의 Mohammed와 함께 최신 탐색 기능, 컬렉션 보기, 메뉴 및 편집 API를 살펴보고 강력한 iPad 앱을 빌드하기 위한 모범 사례를 알아보세요. 이 세션에서 실시간으로 코딩을 실습하거나 샘플 앱을 다운로드하여 나만의 코드를 업데이트하기 위한 참조용으로 사용할 수 있습니다.
리소스
- Building a desktop-class iPad app
- centerItemGroups
- collectionView(_:contextMenuConfigurationForItemsAt:point:)
- collectionView(_:performPrimaryActionForItemAt:)
- Supporting desktop-class features in your iPad app
- titleMenuProvider
- UIDocumentProperties
- UINavigationItem.ItemStyle
- UINavigationItemRenameDelegate
관련 비디오
WWDC23
WWDC22
-
다운로드
♪ ♪
안녕하세요 UIKit의 모하메드입니다 데스크톱 수준의 iPad 앱 만들기 세션에 오신 걸 환영합니다 이 영상에서는 iPadOS 16 API를 이용하여 기존 iPad 앱을 데스크톱 수준으로 업데이트할 예정이죠 새로운 내비게이션 바 API를 통해 강력한 기능을 활용하고 UI 밀도를 높이고 커스터마이징을 제공할 겁니다
새로운 UICollectionView와 메뉴 API를 적용하여 복잡한 작업 흐름과 다중 선택의 빠른 액션을 지원하고 개편된 찾아 바꾸기 기능을 소개하고 텍스트 편집을 개선하는 편집 메뉴로 마무리하죠 우리가 업데이트할 앱은 iPadOS 15 용 Markdown 에디터죠 개선 절차를 거치면서 각 과정의 모범 사례와 의도를 알려 드림으로써 여러분의 앱을 개선할 때 알아 두면 좋은 방법을 제시하겠습니다
시작하기 전에 입문 영상을 보고 싶다면 '데스크톱 수준의 iPad 만나기'를 통해 UIKit의 새로운 iPadOS API를 확인하시거나 'iPad 앱 디자인 소개'를 통해 데스크톱 수준의 iPad 앱을 설계하는 방법을 알아보십시오 그러면 바로 시작하겠습니다 먼저 앱 컨트롤의 배치부터 살펴보죠 iPadOS 15를 위해 만든 앱이어서 이미 내비게이션 바에 주요 컨트롤이 노출되어 있고 부수적인 컨트롤은 다양한 메뉴와 팝오버에 있습니다
iPadOS 16에서는 UIKit이 기존 내비게이션 양식을 공식화하고 커스텀 설정이 가능하고 밀집된 2개의 새로운 레이아웃을 추가하죠 이를 통해 콘텐츠에 가장 적합한 레이아웃을 선택할 수 있습니다 UI 전면에 더 많은 기능을 노출할 수도 있죠
내비게이터 앱은 익숙한 Push Pop 모델을 사용합니다 주로 계층적인 데이터를 표시하는 앱에 적합하죠 설정처럼요
Safari나 파일 같은 브라우저는 다수의 문서나 폴더를 탐색하기에 적합합니다
에디터는 집중해서 보거나 단일 문서를 편집하기에 적합하죠
Markdown 에디터에는 이 스타일이 가장 적합합니다
에디터 스타일은 제목을 바 왼쪽으로 정렬하여 새로운 항목을 중앙에 둘 수 있죠 이를 통해 다른 뷰나 메뉴에서 숨겼던 추가 기능을 전면에 노출할 수 있습니다 이 디자인을 최대로 활용하기 위해 몇 가지를 진행하죠 먼저 우리의 필요에 맞게 되돌리기 버튼을 커스터마이징하고 제목 메뉴에 문서 정보와 문서 기능을 추가하겠습니다 새로운 이름 변경 UI로 이름 변경 기능을 추가하고 마지막으로 숨겨져 있던 기능을 바의 중앙으로 옮겨 쉽게 쓸 수 있도록 하죠 먼저 우리가 사용하려는 에디터 스타일을 적용하기 위해 뷰 컨트롤러의 navigationItem의 스타일 속성을 .editor로 바꾸세요
그러면 즉시 제목이 왼쪽으로 정렬되어 중앙 공간을 쓸 수 있죠
이후 뒤에 있는 완료 버튼을 제거하고 새로운 backAction API로 대체하겠습니다 그러면 더 자연스러운 곳에 해당 기능을 배치하여 이 뷰를 닫고 문서 선택 창으로 돌아갈 수 있죠
다음은 앱에 타이틀 메뉴가 필요한지 알아봅시다 타이틀 메뉴는 이름에서 알 수 있듯이 내비게이션 바의 타이틀 화면에 나타나죠 문서의 메타데이터를 보기에 적합한 곳이고 전체 문서에 적용되는 기능을 살펴볼 수 있습니다 여러분의 앱이 문서 기반이 아니면 전체 뷰에 적용되는 기능을 보여 주는 게 좋겠죠 우리 앱은 문서 메뉴의 제목을 활용하여 문서에 관한 유용한 정보를 나타내는 것이 좋습니다 또한 문서를 드래그하는 기능도 제공할 수 있어 공유 기능을 쉽게 사용할 수 있죠 이제 코드를 작성합시다
우리 앱은 UIDocument의 지원을 받으므로 UIDocument의 파일 URL을 사용하여 UIDocumentProperties 객체를 인스턴스화 할 수 있죠
다음은 같은 URL을 사용하여 NSItemProvider를 만들겠습니다
이후 항목 제공자로 UIDragItem을 만들 건데 속성 객체의 dragItemsProvider에서 반환하죠
또한 UIActivityViewController를 구성하는데 사용할 건데 activityViewControllerProvider 속성 객체에서 반환합니다 마지막으로 속성 객체를 에디터 뷰 컨트롤러의 navigationItem의 documentProperties로 설정하죠 우리가 작성한 코드는 문서 제목에 반영되어 이름, 크기, 아이콘 형태 등 문서에 관한 개요를 제공합니다 드래그 항목과 활성 뷰 컨트롤러 제공자를 지정했으므로 아이콘을 드래그하여 앱 외부로 복사하거나 공유 버튼을 탭 하여 활성 뷰 컨트롤러를 열 수 있죠
문서 제목을 보여 주는 것에 더하여 타이틀 메뉴는 문서 전체에 적용되는 기능을 제공하기에 알맞은 곳입니다 이 메뉴에 보여 줄 수 있는 두 가지 기능이 있는데 시스템에서 제공한 로컬 타이틀과 심벌 이미지가 있고 앱에서 제공하는 커스텀 기능이 있죠
추가 행위가 포함된 이름 바꾸기 기능부터 시작할게요 메뉴에 이 기능을 추가하려면 이름 변경 대리자 프로토콜에 맞추면 됩니다 트리거 됐을 때 바에 내장된 이름 변경 UI가 나타나죠
내비게이션 항목 renameDelegate를 뷰 컨트롤러로 할당하세요
그리고 navigationItemDidEnd RenamingWithTitle을 적용하여 현재 문서의 이름 변경을 처리하죠
이름 변경 기능을 실행하면 이 함수가 호출됩니다 실제로 문서 이름을 바꾸는 건 앱에서 처리해야 하죠 API를 일부러 공개하여 모든 앱의 데이터 모델 유형을 지원하고자 합니다 시스템에서 제공하는 다른 기능으로 넘어가려면 에디터 뷰 컨트롤러의 기능을 우선해야 하죠 여기에서는 복제와 이동 기능을 적용했습니다 UIKit는 자동으로 시스템 제공 기능을 보여 주는데 이름 변경 기능이 포함돼 있으며 navigationItem의 titleMenuProvider에 추천 UIMenuElements 배열로 나타납니다 타이틀 메뉴에 이 기능을 포함하려면 반환한 메뉴의 자식으로 추가하면 되죠
시스템에서 제공하는 기능에 더하여 완전한 커스텀 기능이나 전체 메뉴 계층 구조를 추가할 수도 있습니다 여기 내보내기 하위 메뉴로 HTML, PDF 형식을 추가했는데
이제 타이틀 뷰를 탭 하면 메뉴가 나타나면서 문서 제목과 방금 추가한 기능을 볼 수 있죠 이름 변경을 선택하면 내장된 이름 변경 UI가 활성화되어 문서 이름을 바꿀 수 있습니다
이제 앱의 기본 구조가 잡히기 시작했으니 Mac Catalyst로 앱을 빌드했을 때 어떤 모습인지 살펴보도록 하죠 Mac에서 앱을 실행하면 제목이 왼쪽으로 정렬된 에디터 스타일이 적용된 걸 볼 수 있습니다
되돌리기 기능도 그대로 적용되었고 클릭하면 파일 브라우저가 나타나죠
시스템에서 제공한 기능과 이름 변경 기능도 앱의 파일 메뉴에 자동으로 나타납니다 titleMenuProvider는 Mac Catalyst에서 호출하지 않아서 커스텀 기능은 파일 메뉴에 포함돼 있지 않죠 이 기능이 노출하려면 UIMenuSystem을 이용하여 앱의 메인 메뉴에 수동으로 추가해야 합니다
이제 개선 작업을 계속하죠 목표를 향해 작업하면서 Mac도 계속 확인하겠습니다 일단 바의 중앙 구역을 활용해 보도록 하죠 앱의 iOS 15 버전에는 부수적인 컨트롤과 도구를 포함하는 메뉴가 있습니다 이제 중앙 공간을 활용하여 도구를 눈에 띄게 할 수 있죠
중앙 공간에 커스텀으로 많은 컨트롤을 집어넣어도 불필요한 UI를 채우는 걸 걱정하지 않아도 됩니다 사람마다 작업에 맞게 바의 구성을 바꿀 수 있죠 커스텀 설정을 활용하려면 customizationIdentifier를 내비게이션 항목에 지정하세요
다음은 중앙 항목을 UIBarButtonItemGroups로 정의하죠 그룹은 기존 개념을 UINavigationBar로 확장한 거고 iOS 16의 커스텀 설정을 지원하기 위해 개선됐습니다 이 스크린숏은 기본값으로 지정된 중앙 항목이죠 가장 왼쪽의 스크롤 동기화 버튼은 다른 방법으로는 접근할 수 없는 중요한 기능을 제공합니다 따라서 UIBarButtonItem의 creatingFixedGroup() 함수를 통해 고정 그룹에 배치하면 좋겠죠 고정 그룹으로 지정하면 사용자가 움직일 수 없습니다
한편, 링크 추가 버튼은 중요한 기능이 아니죠 에디터에서 링크 태그를 입력하여 수행할 수 있습니다 creatingOptionalGroup에 넣어 커스텀 설정이 가능하게 할게요 독특한 customizationIdentifier를 사용하여 앱을 다시 실행해도 커스텀 설정이 유지됩니다
기본값의 나머지 항목에도 비슷한 절차를 적용하여 기본값으로 제공할 필요가 없는 낮은 우선순위의 항목으로 넘어갈게요 그중 하나는 텍스트 형식 그룹으로 볼드, 이탤릭, 밑줄 항목을 포함합니다
기본값으로 보여 줄 만큼 중요하지는 않지만 커스텀 팝오버에 포함하여 바에 드래그할 수 있도록 하죠
UIBarButtonItemGroup의 optionalGroup 이니셜라이저인 isInDefaultCustomization를 false로 설정하면 됩니다
그룹에 대표 항목을 설정하여 팝오버에 제목을 붙이고 바에 공간이 부족할 때 작은 크기로 축소할 수 있게 하겠습니다
다시 iPad로 돌아오면 우리가 정의한 중앙 항목이 바의 중앙에 나타나 있죠 새로 추가한 더 보기 버튼을 클릭하면 '도구모음 커스텀 설정' 메뉴가 나타나는데 이걸 클릭하면 커스텀 설정 모드가 활성화됩니다
고정 항목인 스크롤 동기화 버튼은 흐려지고 움직일 수 없지만 다른 항목은 흔들리며 커스텀 설정이 가능함을 나타내죠
형식 그룹 같은 선택 항목은 팝오버에 나타나며 바 안으로 드래그할 수 있습니다
Mac에서 앱을 실행하면 중앙 항목이 커스텀 설정이 가능한 macOS 도구모음 버튼으로 변환됐죠
더 진행하기 전에 iPad로 돌아가서 앱의 크기를 변경할게요 이제 도구모음의 공간이 줄어들어서 중앙 항목을 볼 수 없습니다 UIKit는 남은 공간에 맞춰 중앙 항목을 자동으로 노출했다가 숨기죠 공간에 들어가지 않는 항목은 오버플로 메뉴에 나타납니다 일반적인 바 버튼 항목은 메뉴 항목으로 자동 변환되지만 커스텀 메뉴 형태로 제공할 수도 있죠 UIKit는 커스텀 뷰 항목의 목적을 모르기 때문에 슬라이더 항목이 자동 변환되지 않습니다 메뉴의 형태를 수동으로 지정해야 하죠
슬라이더 항목입니다 커스텀 뷰가 있는 단일 바 버튼 항목으로 선택 바 버튼 그룹에 들어가 있죠 슬라이더의 핵심 기능을 제공하기 위해 메뉴의 형태를 UIMenu의 감소, 초기화, 증가 기능으로 정의하였습니다
UIMenu의 preferredElementSize 속성을 이용하면 메뉴를 더 좁은 공간에 나타낼 수 있죠
keepsMenuPresented 속성을 이용하면 각 기능을 수행한 후에도 메뉴의 모습을 유지하여 글꼴 크기를 여러 번 변경해도 메뉴가 꺼졌다 켜지지 않게 할 수 있습니다 다시 iPad에서 실행해 보죠 오버플로 메뉴를 불러오면 메뉴 속 슬라이더에 3개의 기능이 나란히 있어 슬라이더의 전체 기능을 대신할 수 있습니다
Mac에는 작은 크기가 존재하지 않으므로 이 기능은 macOS의 일반적인 메뉴 항목으로 나타나죠 지금까지 UI 정리 및 커스텀 설정이었습니다 이제 새로운 기능인 컬렉션 뷰와 메뉴 API로 앱의 작업 흐름을 개선하는 법을 살펴보죠 이 앱은 목차 사이드바를 활용하여 빠르게 문서를 탐색하거나 상위 태그를 이용할 수 있습니다 iOS 16 이전에 다중 항목 편집 기능을 추가하려면 전용 편집 모드를 적용해야 했습니다 도구모음의 버튼에 다중 편집 기능이 들어갔겠죠
iOS 16은 다중 항목 메뉴를 새롭게 설계하여 메뉴에 영향을 받는 항목이 무엇인지 명확하게 알 수 있고 다중 항목의 드래그로 직접 전환할 수 있습니다 데스크톱 클래스의 iPad 앱에서 이 메뉴 디자인은 가벼운 선택 방식과 함께 사용할 수 있죠 여기서 '가볍다'라는 표현은 여러 항목을 선택할 때 컬렉션 뷰가 편집 모드로 바뀌거나 앱 UI에 큰 변화를 주지 않는다는 의미입니다 기존 API을 이용하여 이 기능과 키보드 포커스를 사용할 수 있죠 먼저 allowsMultipleSelection을 true로 설정하고
allowsFocus를 true로 설정하여 키보드 포커스를 활성화합니다
selectionFollowsFocus가 true면 드라이버 선택을 허용하죠
iPad에서 실행하면 각 항목이 선택에 추가된 걸 알 수 있고 여전히 선택 액션을 보내서 에디터 뷰가 스크롤 하게 합니다 이제 코드로 돌아가서 어떻게 된 건지 알아보죠
여기 있습니다 didSelectItemAtIndexPath의 코드가 collectionView의 isEditing 속성을 체크하여 편집 모드에 있을 때 스크롤을 허용하지 않으려 하죠 이제 편집 모드가 아닐 때 다중 선택을 허용하므로 이 코드는 모든 선택 때 실행됩니다 UICollectionViewDelegate 메서드를 사용하여 수정할 수 있죠 performPrimaryActionFor ItemAtIndexPath를 적용하고 새 함수로 스크롤 코드를 옮기십시오 단일 항목을 탭 했을 때만 호출하는 함수고 컬렉션 뷰가 편집하지 않기 때문에 편집 모드를 확인할 필요가 없죠
또한 선택과 관련된 동작이 없으므로 didSelectItemAt indexPath의 적용을 삭제해도 됩니다
다시 iPad를 보면 다중 항목을 선택해도 에디터 뷰에서 해당 텍스트로 스크롤 하지 않죠 이 작업이 끝났으니 메뉴 지원을 추가합시다
UICollectionViewDelegate의 단일 항목 메뉴 메서드는 iPadOS 16부터 사용되지 않죠 대체 메서드가 0부터 다양한 항목의 표시를 지원합니다 indexPaths 배열의 항목 수는 선택한 항목의 수와 메뉴를 호출한 위치에 좌우되죠
배열이 비어 있으면 셀 사이 빈 곳에서 호출한 겁니다
indexPath가 단일이라면 선택 해제된 아이템에서 호출했거나 하나만 선택된 거죠
항목이 1개보다 많다면 다중 선택에 포함된 항목에서 호출한 겁니다
iPad로 돌아가서 상위 4개 항목을 선택하고 선택 항목 중 하나를 두 손가락으로 터치하면 다중 항목 메뉴가 나타나죠
같은 동작을 Mac에서 하면 선택 셀 주변에 링을 그려 강조합니다
다중 항목 메뉴가 끝났으니 찾아 바꾸기와 편집 메뉴 기능을 이용한 텍스트 편집 개선에 관해 살펴보죠 우리 앱은 에디터로 UITextView를 사용하고 커스텀 찾아 바꾸기 동작을 요구하지 않으므로 기본 기능을 활성화하기 위해 텍스트 뷰의 isFindInteractionEnabled 속성을 true로 설정하면 됩니다 이를 설정하면 텍스트 편집 중 커맨드 F를 눌렀을 때 찾아 바꾸기 UI가 나타납니다
텍스트 뷰의 편집 메뉴에 커스텀 액션을 추가하는 건 쉽고 빠른 편집 기능을 활용할 수 있죠 우리가 적용할 매서드는 UITextView 대리자 메서드로 editMenuForTextIn range의 suggestedActions이죠 적용 단계에서 UIMenu를 반환하여 숨기기와 같은 커스텀 기능을 시스템 메뉴와 혼합합니다
이게 그 결과입니다 텍스트를 선택하여 편집 메뉴를 띄웠을 때 커스텀 기능과 시스템 제공 기능이 나타나죠 찾아 바꾸기와 편집 메뉴에 관한 정보가 궁금하시면 '데스크톱 수준의 편집 인터랙션 적용하기'를 시청하세요 이제 끝났습니다 몇 가지 수정 사항으로 기본적인 단계를 거쳐 앱을 데스크톱 수준으로 만들고 Mac으로 매끄럽게 전환하는 방법을 알아봤죠 iPadOS 16에서 제공하는 API를 활용하여 여러분의 앱에도 비슷한 작업을 해 보세요 먼저 여러분의 앱에 맞는 내비게이션 스타일을 선택하십시오 문서 속성과 타이틀 메뉴로 문서 작업 흐름을 개선하세요 중앙 항목으로 주요 기능과 커스텀 기능을 제공하십시오 다중 항목 메뉴를 사용하여 빠른 동작을 활성화하고 찾아 바꾸기와 새로운 편집 메뉴로 텍스트 편집을 개선하세요 새로운 앱을 만들거나 기존 앱을 업데이트할 때 새로운 도구를 활용한 앱을 빨리 사용해 보고 싶습니다 시청해 주셔서 감사합니다
-
-
3:36 - Enable UINavigationBar editor style.
navigationItem.style = .editor
-
3:52 - Set a back action.
navigationItem.backAction = UIAction(…)
-
4:48 - Create a document properties header.
let properties = UIDocumentProperties(url: document.fileURL) if let itemProvider = NSItemProvider(contentsOf: document.fileURL) { properties.dragItemsProvider = { _ in [UIDragItem(itemProvider: itemProvider)] } properties.activityViewControllerProvider = { UIActivityViewController(activityItems: [itemProvider], applicationActivities: nil) } } navigationItem.documentProperties = properties
-
6:36 - Adopt rename title menu action and rename UI
override func viewDidLoad() { navigationItem.renameDelegate = self } func navigationItem(_ navigationItem: UINavigationItem, didEndRenamingWith title: String) { // Rename document using methods appropriate to the app’s data model }
-
7:09 - Adopt system provided title menu actions.
override func duplicate(_ sender: Any?) { // Duplicate document } override func move(_ sender: Any?) { // Move document } func didOpenDocument() { ... navigationItem.titleMenuProvider = { [unowned self] suggested in var children = suggested ... return UIMenu(children: children) } }
-
7:10 - Add custom title menu actions
func didOpenDocument() { ... navigationItem.titleMenuProvider = { [unowned self] suggested in var children = suggested children += [ UIMenu(title: "Export…", image: UIImage(systemName: "arrow.up.forward.square"), children: [ UIAction(title: "HTML", image: UIImage(systemName: "safari")) { ... }, UIAction(title: "PDF", image: UIImage(systemName: "doc")) { ... } ]) ] return UIMenu(children: children) } }
-
9:35 - Enable customization for center items
navigationItem.customizationIdentifier = "editorView"
-
10:00 - Define a fixed center item group.
UIBarButtonItem(title: "Sync Scrolling", ...).creatingFixedGroup()
-
10:23 - Define an optional (customizable) center item group.
UIBarButtonItem(title: "Add Link", ...).creatingOptionalGroup(customizationIdentifier: "addLink")
-
10:56 - Define a non-default optional center item group.
UIBarButtonItemGroup.optionalGroup(customizationIdentifier: "textFormat", isInDefaultCustomization: false, representativeItem: UIBarButtonItem(title: "Format", ...) items: [ UIBarButtonItem(title: "Bold", ...), UIBarButtonItem(title: "Italics", ...), UIBarButtonItem(title: "Underline", ...), ])
-
13:16 - Define a custom menu representation for a bar button item group.
sliderGroup.menuRepresentation = UIMenu(title: "Text Size", preferredElementSize: .small, children: [ UIAction(title: "Decrease", image: UIImage(systemName: "minus.magnifyingglass"), attributes: .keepsMenuPresented) { ... }, UIAction(title: "Reset", image: UIImage(systemName: "1.magnifyingglass"), attributes: .keepsMenuPresented) { ... }, UIAction(title: "Increase", image: UIImage(systemName: "plus.magnifyingglass"), attributes: .keepsMenuPresented) { ... }, ])
-
15:10 - Enable multiple selection and keyboard focus in a UICollectionView.
// Enable multiple selection collectionView.allowsMultipleSelection = true // Enable keyboard focus collectionView.allowsFocus = true // Allow keyboard focus to drive selection collectionView.selectionFollowsFocus = true
-
16:11 - Add a primary action to UICollectionView items.
func collectionView(_ collectionView: UICollectionView, performPrimaryActionForItemAt indexPath: IndexPath) { // Scroll to the tapped element if let element = dataSource.itemIdentifier(for: indexPath) { delegate?.outline(self, didChoose: element) } }
-
16:56 - Add a multi-item menu to UICollectionView.
func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemsAt indexPaths: [IndexPath], point: CGPoint) -> UIContextMenuConfiguration? { if indexPaths.count == 0 { // Construct an empty space menu } else if indexPaths.count == 1 { // Construct a single item menu } else { // Construct a multi-item menu } }
-
18:12 - Enable Find and Replace in UITextView.
textView.isFindInteractionEnabled = true
-
18:34 - Add custom actions to UITextView's edit menu.
func textView(_ textView: UITextView, editMenuForTextIn range: NSRange, suggestedActions: [UIMenuElement]) -> UIMenu? { if textView.selectedRange.length > 0 { let customActions = [ UIAction(title: "Hide", ... ) { ... } ] return UIMenu(children: customActions + suggestedActions) } return nil }
-
-
찾고 계신 콘텐츠가 있나요? 위에 주제를 입력하고 원하는 내용을 바로 검색해 보세요.
쿼리를 제출하는 중에 오류가 발생했습니다. 인터넷 연결을 확인하고 다시 시도해 주세요.