Accessibility Label은 VoiceOver (iOS) 또는 TalkBack (Android)이 포커스시 음성으로 알려주는 인터페이스 요소의 이름입니다. iOS에서는 프로퍼티가 accessibilityLabel라고 하며, Android에서는 텍스트를 포함하지 않는 요소에 대해 contentDescription를 사용합니다. Apple Developer Documentation, 2024에 따르면, 레블은 접근성의 기초입니다: 이것이 없으면 사용자가 요소를 식별할 수 없습니다. 레블은 화면 내에서 고유해야 하며 요소의 본질을 알알리 언어로 반영해야 합니다.
주요 포인트
Accessibility Label은 보조 기술을 위한 요소의 이름을 정의하는 문자열 프로퍼티입니다. 사용자가 VoiceOver를 켜고 화면을 스와이프하면, 스크린 리더가 포커스된 요소의 Label을 읽어줍니다. 레블이 없으면 사용자는 요소 유형만 듣게됩니다: “버튼”, “이미지” — 목적이 없이요.
Google I/O 2024, “Accessibility Testing”에 따르면, 스토어 애프리의 35% 중대 접근성 위반이 레블 부재 또는 부정확성과 관련되어 있습니다. Android에서 Accessibility Scanner는 레블 부재를 가장 높은 심각도 오류로 감지합니다.
근본적인 제한: 레블은 요소 유형을 포함하지 않아야 합니다. VoiceOver와 TalkBack은 안내에 자동으로 역할(버튼, 제목, 링크)을 추가합니다. Label에 “보내기 버튼”이 포함된 경우, 사용자는 “보내기 버튼, 버튼”으로 들리게 됩니다 — 중복입니다.
WCAG 4.1.2(레벨 A)는 모든 사용자 인터페이스 요소가 프로그래밍으로 판별 가능한 이름, 역함, 값을 가지도록 요구합니다. Accessibility Label이 이름을 제공합니다. Label이 없으면 기준이 위반된 것으로 간주되며 애프리은 기본 인증을 통과하지 못합니다.
iOS에서 accessibilityLabel은 UIAccessibility 프로토콜로부터 모든 UIView가 상속받습니다. 요소가 텍스트를 포함하는 경우(제목이 있는 UIButton, 텍스트가 있는 UILabel), Label은 자동으로 해당 텍스트로 설정됩니다. UIImageView, 커스텀 컨트롤 및 컨테이너의 경우 Label을 수동으로 설정해야 합니다.
커스텀 테이블 셀 예시:
class CustomTableViewCell: UITableViewCell {
let titleLabel = UILabel()
let priceLabel = UILabel()
override func awakeFromNib() {
super.awakeFromNib()
self.isAccessibilityElement = true
self.accessibilityLabel =
"\(titleLabel.text ?? "") - \(priceLabel.text ?? "")"
}
}
커스텀 UIView의 경우 accessibilityLabel 겟터를 오버라이드할 수 있습니다:
class RatingView: UIView {
var rating: Int = 5
override var accessibilityLabel: String? {
get { return "평점: \(rating)/5" }
set {}
}
}
Apple HIG, 2024의 권고: 요소가 여러 하위 요소(예: 이름과 가격이 있는 상품 카드)로 구성된 경우, 이들을 하나의 복합 Label을 가진 단일 접근성 요소로 결합하십시오. 부모 요소에서 isAccessibilityElement = true를, 자식 요소에서는 false를 설정하십시오.
UILabel이 NSAttributedString을 사용하는 경우 accessibilityLabel은 기본적으로 .string(일반 텍스트)과 같습니다. 의미가 다른 값(예: 기호 아이콘이 ★ 문자 대신 “별”로 읽히는 경우)을 전달해야 하는 경우 accessibilityLabel을 명시적으로 설정하십시오. VoiceOver는 Unicode 문자를 의미 있게 읽지 못합니다.
Android에서 contentDescription은 ImageView, ImageButton 및 커스텀 View에 대한 Label 역할을 합니다. 내장 텍스트가 있는 TextView 및 Button의 경우 contentDescription을 설정할 필요가 없습니다 — TalkBack이 자동으로 텍스트를 읽습니다.
Kotlin을 통한 프로그래밍 설정:
binding.iconStar.contentDescription = "즐겨찾기에 있는 상품"
// 여러 요소가 있는 커스텀 View용
binding.customCard.setContentDescription(
"\(title) 을/를 \(price)에")
장식 요소에 대한 XML:
<ImageView
android:contentDescription="@null"
android:src="@drawable/divider"
android:importantForAccessibility="no" />
importantForAccessibility = “no” 프로퍼티는 요소를 접근성 트리에서 완전히 제외합니다. iOS에서 이에 해당하는 것은 isAccessibilityElement = false입니다.
Jetpack Compose에서 Label은 semantics 수정자를 통해 설정됩니다:
Image(
painter = painterResource(R.drawable.ic_search),
contentDescription = "상품 검색",
modifier = Modifier.semantics {
contentDescription = "상품 검색"
}
)
Compose에서 contentDescription은 Image에 대한 필수 매개변수입니다 — 이것이 없으면 코드가 컴파일되지 않습니다(경고). 이로써 API 설계를 통해 접근성이 강제적으로 향상됩니다.
Accessibility Label은 “이 요소는 뭔가요?”라는 질문에 답합니다. Hint(iOS에서 accessibilityHint, Android에서 contentDescription 내 추가 텍스트)는 “상호작용하면 무엇이 일어나나요?”를 설명합니다. VoiceOver는 이것들을 순차대로 알려줍니다: 먼저 Label, 그다음 Hint.
삭제 버튼 예시:
Deque University, 2024에 따르면, Label과 Hint를 적절히 분리하면 VoiceOver 사용자의 작업 완료률이 28% 향상됩니다. 인지 장애가 있는 사용자는 특히 Hint에 의존합니다: 설명 없이 “삭제”를 누른 것에 대해 불확실할 경우 40%가 작업을 거부합니다.
흘한 실수: Label에 “삭제” 대신 “삭제 버튼”을 작성하는 것입니다. 요소 유형(버튼)은 VoiceOver에 의해 트레이트를 통해 자동으로 추가됩니다. 그 결과 사용자는 “삭제 버튼, 버튼”으로 듣게 됩니다 — 중복입니다. 올바른 Label: “삭제”, Hint: “선택한 사진을 삭제합니다”.
레블 로케일리이션은 필수입니다 — 표준 메커니즘을 통해 진행됩니다: iOS에서 NSLocalizedString, Android에서 문자열 리소스 @string/. 로케일리이션 없이 영어 연결으로 Label을 설정하지 마십시오.
W3C WCAG 2.2에 기반한 좋은 Label 규칙:
애프리 전체에 걸쳐 Label에 단일 용어집을 사용하십시오. 한 화면에 “즐겨찾기”가 그리고 다른 화면에 “책깔피”가 있으면 사용자가 혼란스럽습니다. 접근성 용어 테이블을 만드십시오 — 디자이너와 로케일라이저와 협의하십시오.
입력 필드(UITextField, EditText)의 경우 Label은 플레이스홀더 또는 필드 레블과 일치해야 합니다. 그러나 텍스트 입력 후 플레이스홀더가 보통 사라집니다. 영구적인 이름에는 accessibilityLabel을, 필드의 현재 내용에는 accessibilityValue를 사용하십시오 — 이것이 WCAG 4.1.2 표준입니다. 해결 방법: accessibilityLabel을 정적으로(필드 레블과 동일하게), accessibilityValue를 동적으로(입력된 텍스트와 동일하게) 설정합니다. iOS에서는 이것이 자동이지만 커스텀 필드의 경우 accessibilityValue를 오버라이드하여 수동으로 설정합니다. VoiceOver가 “, 텍스트 필드” 대신 “이메일, 예시@도메인.com, 텍스트 필드”를 읽도록 확인하십시오.
자동화된 테스트가 모든 화면에서 Label 정확성을 보장하는 유일한 방법입니다. iOS는 .label에 접근할 수 있는 XCUIApplication을 제공하고, Android는 AccessibilityCheckRule과 setContentDescription을 제공합니다.
iOS 테스트 예시:
func testLabelsAreUnique() {
let app = XCUIApplication()
app.launch()
let allButtons = app.buttons.allElementsBoundByIndex
let labels = allButtons.compactMap { $0.label }
let uniqueLabels = Set(labels)
XCTAssertEqual(labels.count, uniqueLabels.count,
"중복된 Label이 발견되었습니다")
}
Espresso를 사용한 Android 예시:
@Test
fun testButtonHasAccessibilityLabel() {
onView(withId(R.id.btnSubmit))
.check(matches(
withContentDescription(containsString("보내기"))
))
}
수동 테스트: VoiceOver(iOS) 또는 TalkBack(Android)을 켜고 화면의 모든 요소를 오른쪽으로 스와이프하십시오. 각 요소는 의미 있는 안내를 받아야 합니다. “버튼” 또는 “이미지”만 듣리는 경우 Label이 부재한 것입니다.
Label을 설정한 후 VoiceOver 사용자는 로터를 사용하여 권욕 네비게이션을 할 수 있습니다: “버튼”, “제목”, “링크” 등의 모드. Label이 올바게 설정된 경우 VoiceOver는 해당 로터 모드에 요소를 포함시킵니다. 모든 버튼이 “버튼” 모드에서, 모든 제목이 “제목” 모드에서 표시되는지 확인하십시오.
Label은 VoiceOver 검색에도 영향을 미칩니다. 사용자가 검색 모드에서 단어를 입력하면 VoiceOver는 일치하는 Label이 있는 요소로 포커스를 이동시킵니다. 따라서 Label에는 사용자가 검색할 키워드가 포함되어야 합니다.
파이프라인에 Label 검사를 추가하십시오. iOS에서는 fastlane scan과 함께 XCUITest를 사용하십시오. Android에서는 빈 contentDescription을 감지하는 AccessibilityCheckRule을 통해 Accessibility Test Framework를 사용하십시오. 이를 통해 새로운 화면을 머지할 때 퇼밴을 방지할 수 있습니다.
자주 묻는 질문
Label은 요소를 식별하고(“검색”), Hint는 작업 결과를 설명합니다(“검색 화면을 엽니다”). VoiceOver는 포커스시 Label을 즉시 알리고, 상세 설명 모드에서 Hint를 알립니다.
iOS에서 UILabel은 자동으로 텍스트와 동일한 accessibilityLabel을 얻습니다. 추가 설정이 필요하지 않습니다. Android에서 TextView도 비슷하게 동작합니다.
부모 View에서 isAccessibilityElement = true를 설정하고 자식 요소로부터 결합된 텍스트를 반환하도록 accessibilityLabel을 오버라이드합니다. 복잡한 컴포넌트의 경우 구분자를 사용한 결합을 사용합니다.
반복되는 요소에 맥락을 추가하십시오: “iPhone 15 구매”, “iPhone 15 Pro 구매”. UI 테스트를 통해 검사를 자동화합니다 — 모든 Label을 수집하고 중복이 없는지 확인합니다.
아니요. 요소를 숨기려면 iOS에서 isAccessibilityElement = false 또는 Android에서 importantForAccessibility = “no”를 사용합니다. 빈 Label은 요소를 숨기지 못합니다 — 스크린 리더가 “제목 없음”으로 읽습니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.