SwiftUI 접근성 내부
Source: Dev.to
Accessibility Is a Parallel View Tree
SwiftUI builds two trees:
- The visual view tree
- The accessibility tree
They are related — but not identical. A single visual view can:
- expose multiple accessibility elements
- merge with siblings
- be hidden entirely
- change role dynamically
Understanding this explains most “why doesn’t VoiceOver read this correctly?” bugs.
SwiftUI가 접근성 요소를 생성하는 방법
기본 설정:
- 대부분의 컨트롤(
Button,Toggle,TextField)은 접근성 요소를 자동으로 생성합니다. - 컨테이너(
HStack,VStack,ZStack)는 일반적으로 생성되지 않습니다.
Example
HStack {
Image(systemName: "heart.fill")
Text("Favorites")
}
VoiceOver는 SwiftUI에 별도로 지시하지 않는 한 “heart fill, Favorites”라고 읽을 수 있습니다.
그룹화 vs 분리 요소
SwiftUI는 그룹화에 대한 명시적인 제어를 제공합니다.
자식들을 하나의 요소로 결합
.accessibilityElement(children: .combine)
결과: “Favorites, button”
자식들을 완전히 무시
.accessibilityElement(children: .ignore)
이제 모든 것을 수동으로 정의합니다.
자식들을 별도로 포함 (기본값)
.accessibilityElement(children: .contain)
각 자식이 자체 의미를 가질 때 사용합니다.
역할, 특성 및 의미론
접근성은 단순히 레이블이 아니라 의미입니다. SwiftUI는 동작을 설명하기 위해 특성을 사용합니다:
isButtonisHeaderisSelectedisDisabled
예시
Text("Settings")
.accessibilityAddTraits(.isHeader)
이제 VoiceOver는 텍스트뿐만 아니라 계층 구조를 이해합니다.
포커스 시스템 (탐색에 필수)
SwiftUI의 접근성 포커스는 상태 기반입니다.
@AccessibilityFocusState var focused: Bool
Text("Error occurred")
.accessibilityFocused($focused)
포커스 트리거:
focused = true
필수 사용 사례:
- 폼 검증 오류
- 탐색 전환
- 알림 및 시트
- 동적 콘텐츠 업데이트
포커스 제어가 없으면 사용자가 길을 잃게 됩니다.
상태 변화 및 접근성 업데이트
SwiftUI는 다음과 같은 경우 자동으로 변경 사항을 알립니다:
- 텍스트 변경
- 값 업데이트
- 컨트롤 토글
사용자 정의 뷰는 명시적인 알림이 필요합니다:
UIAccessibility.post(
notification: .announcement,
argument: "Upload complete"
)
필요할 때만 적게 사용하세요 — 하지만 의도적으로 사용하십시오.
접근성 및 NavigationStack
Navigation은 접근성 트리에 영향을 줍니다. 탐색할 때:
- 이전 요소가 제거됩니다
- 새로운 트리가 구축됩니다
- 제어되지 않는 한 포커스가 초기화됩니다
탐색 후 권장되는 방법:
.accessibilityFocused($focusOnTitle)
이는 UIKit의 “screen changed” 동작을 그대로 반영합니다.
제스처 vs 접근성 액션
사용자 정의 제스처는 기본적으로 접근성이 제공되지 않습니다.
잘못된 패턴
.onTapGesture { submit() }
VoiceOver 사용자는 이를 발견할 수 없습니다.
올바른 패턴
.accessibilityAction {
submit()
}
또는 실제 Button을 사용하세요.
장식 요소 숨기기
장식용 뷰는 접근성에서 보이지 않게 해야 합니다:
Image("background")
.accessibilityHidden(true)
그렇지 않으면 VoiceOver가 의미 없는 내용을 읽어줍니다.
동적 타입은 레이아웃 문제다
Dynamic Type은 단순히 글꼴만이 아니라 레이아웃에도 영향을 줍니다. SwiftUI는 자동으로:
- 글꼴 크기를 늘립니다
- 텍스트를 재배치합니다
- 줄 높이를 조정합니다
레이아웃은 성장할 수 있도록 허용해야 합니다.
잘못된 관행
- 고정 높이
- 잘린 텍스트
- 경직된 스택
올바른 관행
- 유연한 프레임
- 다중 행 텍스트
- 적응형 레이아웃
접근성 테스트 올바르게 하기
- 시뮬레이터: VoiceOver, Dynamic Type, Reduce Motion, Increase Contrast
- Xcode Accessibility Inspector: element order, labels, traits, hit targets
경험법: 탐색이 어색하게 느껴진다면 → 아마도 그렇다.
접근성 디자인 규칙 (내부 수준)
- 접근성은 상태 기반이다
- 포커스는 명시적이다
- 의미론이 레이블보다 중요하다
- 커스텀 뷰는 커스텀 접근성이 필요하다
- 네비게이션은 처리되지 않으면 포커스를 재설정한다
- 제스처는 접근성 액션이 필요하다
- 레이아웃은 다이내믹 타입을 지원해야 한다
Final Thoughts
SwiftUI 접근성은 부가 기능이 아니라 일급 시스템이며 다음과 연결됩니다:
- 렌더링
- 상태
- 내비게이션
- 레이아웃
- 인터랙션
시작부터 접근성을 염두에 두고 디자인하면:
- UI가 더 명확해집니다
- 아키텍처가 개선됩니다
- 앱이 더 “Apple‑like”하게 느껴집니다
- 모두가 혜택을 받습니다 — 보조 기술 사용자뿐만 아니라