스트리밍은 대부분의 브라우저와
Developer 앱에서 사용할 수 있습니다.
-
Dynamic Type 시작하기
Dynamic Type은 사용자가 시스템 전반과 모든 앱에서 원하는 텍스트 크기를 선택할 수 있는 기능입니다. Dynamic Type 지원을 시작하기 위해 Dynamic Type의 작동 방식, 앱 내 텍스트 크기 조절과 관련된 문제를 식별하는 방법, 탁월한 Dynamic Type 경험을 만들기 위한 단계별 SwiftUI 및 UIKIt 활용법 등 기본적인 내용을 알아보세요. 모두가 손쉽게 사용할 수 있는 탐색 제어기를 만들기 위해 큰 콘텐츠 뷰어를 효과적으로 활용하는 방법도 살펴봅니다.
챕터
- 0:00 - Introduction
- 3:11 - Scaled text
- 6:00 - Dynamic layouts
- 8:56 - Images and symbols
- 11:58 - Large content viewer
리소스
-
다운로드
안녕하세요 제 이름은 Gaeth입니다 Accessibility 팀의 엔지니어죠 Apple은 모든 종류의 손쉬운 사용 요구 사항을 중요하게 생각합니다 콘텐츠가 대부분 텍스트로 전달되기 때문에 이 세션에서는 시각 정보 손쉬운 사용에 중요한 품질 앱에서 텍스트를 읽고 탐색하는 방법, Dynamic Type을 사용해 이를 멋진 경험으로 만드는 방법에 대해 설명하겠습니다 Dynamic Type을 사용하면 시스템과 앱에 표시되는 텍스트의 크기를 선택할 수 있습니다 앱의 많은 사용자가 이 설정을 맞춤화합니다 사용자들이 앱에서 제공하는 모든 것을 이용하도록 이 기능을 지원하는 것은 필수입니다 텍스트 크기가 커진 앱이 어떻게 작동하는지 생각해 보신 적이 없다면 제대로 찾아오셨습니다!
동적 UI를 만들면 화면 크기와 방향, 플랫폼에 상관없이 작동하는 인터페이스를 구축할 수도 있습니다 사용자마다 선호하거나 필요한 텍스트 크기가 다르기 때문에 동적 UI는 매우 중요합니다 Dynamic Type은 모든 텍스트 크기에서 가독성을 높입니다 사용자가 필요에 맞는 옵션으로 텍스트 크기를 변경할 수 있습니다 앱에서 더 큰 텍스트를 사용해 본 적이 없다면 손쉬운 사용 설정으로 이동해서 테스트해 보세요 손쉬운 사용 > 디스플레이 및 텍스트 크기 > 더 큰 텍스트로 이동하여 텍스트 크기를 맞춤화합니다 기본적으로 7가지 텍스트 크기 중에서 선택할 수 있습니다 글자 더 크게 조절이 활성화되면 5가지 텍스트 크기가 추가됩니다 제어 센터에 텍스트 크기 제어 항목을 추가해 즉석에서 원하는 크기로 변경할 수도 있습니다
기본적으로 텍스트는 큰 크기로 표시됩니다
텍스트 크기는 변경되면 바로 적용됩니다 이 예에서는 설정 앱에서 더 큰 텍스트 크기로 변경하면 제목, 본문, 각 셀의 레이블을 포함해 나타나는 각 텍스트 보기의 크기가 자동으로 커집니다 이 표 보기 레이아웃의 각 하위 보기의 크기도 커져서 커진 콘텐츠 크기에 맞춰집니다 이 예에서는 손쉬운 사용 텍스트 크기가 선택되어 있고 콘텐츠가 디스플레이 경계를 넘어가서 스크롤만으로 모든 텍스트를 읽을 수 있습니다 이 비디오에서는 Dynamic Type으로 앱의 콘텐츠 크기를 조절하고 최상의 경험을 선사할 수 있는 기법 몇 가지를 다루겠습니다 먼저 Dynamic Type 사용 방법을 살펴보겠습니다 그 다음, 커진 텍스트의 레이아웃 조절 방법을 설명하겠습니다 인라인 이미지와 기호에 대한 옵션을 살펴보고 마지막으로, 나머지 콘텐츠에 맞춰 크기 조절이 안되는 제어 항목에 대해 큰 콘텐츠 뷰어 활용 방법을 설명하겠습니다
먼저, 텍스트 크기 조절 방법을 살펴보겠습니다 앱에서 Dynamic Type에 대한 멋진 경험은 내장 텍스트 스타일을 활용하는 것부터 시작됩니다 고정된 서체를 제공하는 대신 앱이 시스템 제공 텍스트 스타일 하나를 선택합니다 예를 들어 본문 텍스트 스타일은 여러 텍스트 줄에 걸쳐 편안한 읽기 경험을 선사합니다 헤드라인 스타일은 제목을 주변 콘텐츠와 구별할 수 있습니다 앱의 텍스트는 이런 스타일을 사용해 사용자가 기기에서 선택하는 다양한 텍스트 크기에 맞춰 자동으로 조절되지만 콘텐츠의 시각적 계층은 그대로 유지됩니다
SwiftUI에서는 서체 한정자로 내장 텍스트 스타일을 사용합니다 가령 제목 스타일을 선택하려면 title 매개변수를 전달합니다 UIKit에서 텍스트 스타일을 사용하려면 UILabel에 대해 adjustsFontForContentSizeCategory 속성을 true로 설정해 시스템의 텍스트 크기가 변경되면 레이블의 서체도 자동 업데이트되게 합니다 그런 다음 원하는 스타일을 가진 textStyle에 대해 preferredFont를 사용해 서체를 설정합니다 텍스트에 필요한 만큼의 줄 수를 사용하고 잘리지 않도록 레이블의 줄 수를 0으로 설정하는 것을 고려합니다 Dynamic Type을 사용한 앱을 검사할 때 확인해야 하는 몇 가지 유형의 문제가 있습니다 예를 들어 큰 텍스트는 표시할 텍스트 줄이 부족하면 잘릴 수 있습니다 컨테이너 프레임이 고정되어 있어 텍스트가 잘려서 표시될 수도 있죠 다행히 이런 문제는 시작할 때 발견하기가 쉽습니다 Xcode 미리보기를 사용하면 쉽게 발견할 수 있죠 SwiftUI를 사용할 경우 Xcode에서 미리보기 캔버스로 이동해 Variants 버튼을 클릭합니다 그런 다음 Dynamic Type Variants를 선택합니다
Xcode가 모든 텍스트 크기 변형의 미리보기를 생성하므로 특정 보기의 문제를 빠르게 찾을 수 있습니다 또는 Xcode 캔버스에서 설정 버튼을 클릭해 특정 텍스트 크기를 선택합니다 Xcode 디버거를 사용해 텍스트 크기를 테스트할 수도 있습니다 설정 아이콘을 클릭해 Dynamic Type과 기타 손쉬운 사용 설정을 무효화할 수 있습니다 손쉬운 사용 문제에 대해 앱 점검을 수행할 수도 있습니다 점검을 통해 앱의 보기 계층을 검사하고 잘린 텍스트, 유실된 레이블 낮은 명암비 등 다양한 손쉬운 사용 문제를 식별합니다
시스템 서체와 텍스트 스타일을 사용하는 것은 Dynamic Type을 시작하는 좋은 방법입니다 큰 텍스트로 앱 경험을 개선할 때 최상의 경험을 위해 콘텐츠 레이아웃 조정도 고려할 수 있습니다 예를 들어 연락처 앱에서 새 연락처 포스터를 만들 때 포스터 옵션이 가로 스택으로 표시됩니다 텍스트 크기가 커지면 레이아웃이 동적으로 세로 스택으로 전환하여 각 셀이 디스플레이의 폭 전체를 차지합니다
이와 같은 경우 더 큰 텍스트에 반응해 레이아웃은 변경하되 레이아웃의 크기는 기본 크기나 더 작은 크기로 유지하고자 할 수 있습니다
그림 네 개가 가로 스택으로 정렬된 이 앱을 생각해 보세요 각 그림 아래에 레이블이 있습니다 Standing Figure Rolling Figure 등이죠 기본 텍스트 크기로 표시된 레이블을 읽을 수 있지만 글자를 더 크게 조절할 때는 적합하지 않습니다 각 그림의 폭이 레이블을 표시하기에는 공간이 부족합니다 개별 셀부터 시작하겠습니다 FigureCell은 이미지와 그 아래의 제목을 포함한 VStack입니다 SwiftUI에서는 동적인 레이아웃을 구현하기 위해 dynamicTypeSize 환경 키 경로를 사용합니다 그런 다음 AnyLayout 유형의 dynamicLayout이라고 하는 속성을 정의합니다 더 큰 글자가 선택되면 이것은 HStackLayout으로 분석되고 다른 텍스트 크기가 선택되면 VStackLayout으로 분석됩니다 다음, 본문 레이아웃을 VStack에서 dynamicLayout으로 업데이트합니다 이제 셀을 세로로 배치해야 합니다 포함하는 보기에서 같은 단계를 따라 콘텐츠 기본 보기를 더 크게 조절된 글자의 경우 VStackLayout으로 그 밖의 경우 HStackLayout으로 전환하게 합니다
좋습니다 자, 텍스트 크기가 더 크게 조절된 글자로 변경되면 레이아웃이 동적으로 변경되어 텍스트에 더 넓은 폭을 제공하여 읽기 쉬우면서도 잘림은 방지합니다
UIKit에서 이렇게 하려면 UIStackView 사용을 고려하세요 스택 보기는 축 속성을 업데이트해 하위 보기를 세로 또는 가로로 배치하는 데 필요한 모든 로직을 제공합니다
축을 결정하려면 보기 제어기의 traitCollection에서 선호하는 콘텐츠 크기 카테고리에 대해 isAccessibilityCategory 속성을 사용합니다
앱이 실행되는 동안 텍스트 크기 변경에 반응하려면 UI 콘텐츠 크기 카테고리인 didChangeNotification을 구독하고 스택 보기를 가장 적합한 축으로 업데이트합니다 더 큰 텍스트를 사용할 경우 레이아웃은 물론 텍스트 콘텐츠와 함께 표시되는 이미지와 기호도 조절해야 합니다 큰 텍스트에서 이미지와 아이콘 작업을 할 때 커진 텍스트에 맞춰 아이콘 크기를 조절하는 동시에 아이콘이 텍스트 공간을 너무 많이 차지하지 않도록 균형을 유지하고 싶을 겁니다 예를 들어 iPhone의 설정 앱을 생각해 보세요 표 보기의 항목마다 텍스트 레이블 및 장식 이미지가 포함되었습니다 텍스트 크기가 커지면 텍스트 레이블은 커져도 장식 이미지는 커지지 않습니다
앱이 장식 보기보다 필수 콘텐츠의 크기 조절을 우선적으로 처리하기 때문이죠 이미지 크기가 조절되지 않는 경우 텍스트는 최대 공간을 사용하려고 이미지 아래로 줄바꿈합니다 텍스트 크기가 커질 때 순전히 장식용인 보기 제거를 고려하는 경우가 드물게 있습니다 하지만 이렇게 함으로써 기능과 필수 콘텐츠가 손실되지 않도록 해야 합니다 SwiftUI와 UIKit에서 비슷한 것을 구현하는 방법을 살펴보겠습니다 SwiftUI에서는 텍스트가 아이콘 아래로 줄바꿈하기 쉽습니다 보기를 목록에 포함하면 추가 작업 없이 텍스트가 아이콘 아래로 줄바꿈합니다 목록 보기 외부에서 텍스트에 직접 이미지를 삽입할 수도 있습니다 UIKit에서는 NSAttributedString을 사용해 이 동작을 구현합니다 NSTextAttachment로 이미지와 함께 속성이 지정된 문자열을 생성하고 이를 속성이 지정된 문자열에 추가합니다
이미지의 크기도 조절되게 하려는 경우가 있는데 특히 이미지에 텍스트 또는 나머지 콘텐츠와 관련된 주요 아이콘이 포함된 경우 그렇습니다
이미지가 SF Symbol이면 크기가 자동으로 조절됩니다 하지만 애셋에 이미지나 PDF가 있는 경우 ScaledMetric API를 사용하면 선택된 텍스트 크기에 따라 이미지 크기가 조절되게 할 수 있습니다 ScaledMetric을 추가하고 이미지의 폭이나 높이를 지정하면 됩니다 이 값은 텍스트 크기가 변경되면 런타임에 자동으로 조절됩니다
굉장하죠! 텍스트 크기를 늘리면 이미지와 텍스트의 크기가 자동으로 조절됩니다
UIKit에서 이 동작을 구현하는 건 UIImage SymbolConfiguration이죠 예를 들어 textStyle과 함께 SymbolConfiguration을 사용해 특정 텍스트 스타일로 구성을 만들 수 있습니다 그런 다음 이 기호 구성으로 UIImage를 생성합니다 마지막으로, 큰 콘텐츠 뷰어를 살펴보겠습니다 큰 콘텐츠 뷰어를 사용하면 글자가 커졌을 때 커지지 않는 제어 항목을 탐색할 수 있습니다 작동 방식을 보여드리겠습니다 iPhone의 시계 앱은 디스플레이 하단에 네 개의 탭이 있습니다 이 탭을 탭하고 누른 채로 있으면 큰 콘텐츠 뷰어가 중앙에 나타나 점검할 탭의 레이블과 아이콘을 크게 볼 수 있습니다
다른 탭을 스와이프하고 손가락을 떼서 해당 탭으로 이동할 수 있습니다
이 경우 탭 막대가 화면 높이의 10% 미만을 차지합니다 큰 텍스트가 활성화되었을 때 탭 막대 높이가 늘어난다면 거의 화면의 1/4을 차지할 것입니다 중점을 두는 앱 콘텐츠가 표시되는지 확인하도록 탭 막대는 항상 나타나 있습니다 시스템에서 제공하는 기본 제어 막대를 사용하는 경우에는 해야 할 일이 없습니다 이미 지원되고 있으니까요 하지만 맞춤형 막대나 보기를 구현할 때는 필요한 경우 큰 콘텐츠 뷰어 채택을 고려하세요
가령 SwiftUI에서는 맞춤형 탭 막대를 이렇게 구성할 수 있습니다 이 코드에서 HStack은 표시되는 각 탭의 버튼을 포함합니다 바인딩을 통해 탭 막대가 선택된 항목을 추적할 수 있습니다
큰 콘텐츠 뷰어 지원을 추가하려면 accessibilityShowsLargeContentViewer 한정자를 사용해 버튼의 이름과 기호 등 뷰어에 표시되는 레이블을 제공합니다
UIKit에서 큰 콘텐츠 뷰어를 지원하려면 UILargeContentViewerItem 프로토콜에 보기를 맞추고 필요한 속성을 구현해 제목, 이미지, 표시할 때를 나타내는 속성을 제공합니다 다음, UILargeContentViewerInteraction 인스턴스를 만들어 보기에 추가합니다 제어 막대에서 자체 제스처 인식기를 사용하는 경우 큰 콘텐츠 뷰어가 먼저 제스처를 처리할 수 있도록 추가 작업을 수행해야 합니다
다른 제스처 인식기로 인식 또는 실패 관계를 설정하려면 큰 콘텐츠 뷰어 상호작용에서 gestureRecognizerForExclusionRelationship을 확인합니다
Dynamic Type으로 시작하는 방법을 알았으니 이제 더 큰 글자를 사용하는 앱을 테스트해 보세요 시스템 정의 텍스트 스타일을 사용하고 레이아웃을 맞춤 설정해 텍스트 가독성을 우선시하려면 어디를 다듬어야 할지 알아보세요 기억하세요, 사용자마다 선호하거나 필요한 텍스트 크기가 다릅니다 앱을 동적으로 만들면 누구든지 앱에서 뛰어난 가독성을 경험하도록 할 수 있습니다 한 단계 더 나아가 UI 테스트에 손쉬운 사용 점검을 빌드해서 앱 반복 시마다 Dynamic Type 문제가 있는지 파악하세요 ‘SwiftUI의 손쉬운 사용 관련 업데이트’ 비디오도 확인해서 SwiftUI의 보조 기술에 대해 자세히 알아보세요 시청해 주셔서 감사합니다!
-
-
3:53 - Built-in text styles with SwiftUI
// Use built-in text styles with SwiftUI import SwiftUI struct ContentView: View { var body: some View { Text("Hello, World!") .font(.title) } }
-
4:06 - Built-in text styles in UIKit
// Built-in text styles in UIKit import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() let label = UILabel(frame: .zero) setupConstraints() label.text = "Hello, World!" label.adjustsFontForContentSizeCategory = true label.font = .preferredFont(forTextStyle: .title1) label.numberOfLines = 0 self.view.addSubview(label) } }
-
7:20 - Dynamic layout in SwiftUI
// Dynamic layout in SwiftUI import SwiftUI struct FigureCell: View { @Environment(\.dynamicTypeSize) private var dynamicTypeSize: DynamicTypeSize var dynamicLayout: AnyLayout { dynamicTypeSize.isAccessibilitySize ? AnyLayout(HStackLayout()) : AnyLayout(VStackLayout()) } let systemImageName: String let imageTitle: String var body: some View { dynamicLayout { FigureImage(systemImageName: systemImageName) FigureTitle(imageTitle: imageTitle) } } }
-
7:52 - Dynamic layout in SwiftUI
// Dynamic layout in SwiftUI import SwiftUI struct FigureContentView: View { @Environment(\.dynamicTypeSize) private var dynamicTypeSize: DynamicTypeSize var dynamicLayout: AnyLayout { dynamicTypeSize.isAccessibilitySize ? AnyLayout(VStackLayout(alignment: .leading)) : AnyLayout(HStackLayout(alignment: .top)) } var body: some View { dynamicLayout { FigureCell(systemImageName: "figure.stand", imageTitle: "Standing Figure") FigureCell(systemImageName: "figure.wave", imageTitle: "Waving Figure") FigureCell(systemImageName: "figure.walk", imageTitle: "Walking Figure") FigureCell(systemImageName: "figure.roll", imageTitle: "Rolling Figure") } } }
-
8:20 - Dynamic layout in UIKit
// Dynamic layout in UIKit import UIKit class ViewController: UIViewController { private var mainStackView: UIStackView = UIStackView() required init?(coder: NSCoder) { super.init(coder: coder) NotificationCenter.default.addObserver(self, selector: #selector(textSizeDidChange(_:)), name: UIContentSizeCategory.didChangeNotification, object: nil) } override func viewDidLoad() { super.viewDidLoad() setupStackView() } @objc private func textSizeDidChange(_ notification: Notification?) { let isAccessibilityCategory = self.traitCollection.preferredContentSizeCategory.isAccessibilityCategory mainStackView.axis = isAccessibilityCategory ? .vertical : .horizontal setupConstraints() } }
-
10:12 - Scale inline images with SwiftUI
// Inline images in SwiftUI import SwiftUI struct ContentView: View { var body: some View { List { FigureListCell(figureName: "Standing Figure", systemImage: "figure.stand") FigureListCell(figureName: "Rolling Figure", systemImage: "figure.roll") FigureListCell(figureName: "Waving Figure", systemImage: "figure.wave") FigureListCell(figureName: "Walking Figure", systemImage: "figure.walk") } } }
-
10:30 - Scale inline images with UIKit
// Inline images in UIKit func attributedStringWithImage(systemImageName: String, imageTitle: String) -> NSAttributedString { let attachment = NSTextAttachment() attachment.image = UIImage(systemName: systemImageName) let attachmentAttributedString = NSMutableAttributedString(attachment: attachment) attachmentAttributedString.append(NSAttributedString(string: imageTitle)) return attachmentAttributedString }
-
11:05 - Scale images in SwiftUI
// Scaling images in SwiftUI import SwiftUI struct ContentView: View { @ScaledMetric var imageWidth = 125.0 var body: some View { VStack { Image("Spatula") .resizable() .aspectRatio(contentMode: .fit) .frame(width: imageWidth) Text("Grill Party!") .frame(alignment: .center) } } }
-
11:38 - Scale symbols with UIKit
// Symbol configuration in UIKit import UIKit func imageWithBodyConfiguration(systemImageName: String) -> UIImage? { let imageConfiguration = UIImage.SymbolConfiguration(textStyle: .body) let configuredImage = UIImage(systemName: systemImageName, withConfiguration: imageConfiguration) return configuredImage }
-
13:15 - Add large content viewer support with SwiftUI
// Large content viewer support in SwiftUI import SwiftUI struct FigureBar: View { @Binding var selectedFigure: Figure var body: some View { HStack { ForEach(Figure.allCases) { figure in FigureButton(figure: figure, isSelected: selectedFigure == figure) .onTapGesture { selectedFigure = figure } .accessibilityShowsLargeContentViewer { Label(figure.imageTitle, systemImage: figure.systemImage) } } } } }
-
13:45 - Add large content viewer support with UIKit
// Large content viewer support in UIKit import UIKit class FigureCell: UIStackView { var systemImageName: String! var imageTitle: String! var imageLabel: UILabel! var titleImageView: UIImageView! required init(coder: NSCoder) { super.init(coder: coder) setupFigureCell() } init(systemImageName: String, imageTitle: String) { super.init(frame: .zero) self.systemImageName = systemImageName self.imageTitle = imageTitle setupFigureCell() self.addInteraction(UILargeContentViewerInteraction()) self.showsLargeContentViewer = true self.largeContentImage = UIImage(systemName: systemImageName) self.scalesLargeContentImage = true self.largeContentTitle = imageTitle } }
-
-
찾고 계신 콘텐츠가 있나요? 위에 주제를 입력하고 원하는 내용을 바로 검색해 보세요.
쿼리를 제출하는 중에 오류가 발생했습니다. 인터넷 연결을 확인하고 다시 시도해 주세요.