반응형

SwiftUI Study – 접근성(Accessibility)을 고려한 UI 설계: Label·Value·Hint·Actions를 활용해 앱 완성도 높이기

 

1. 왜 중요한가 (문제 배경)

SwiftUI는 기본적으로 접근성(VoiceOver 등)을 잘 지원하지만,

다음과 같은 상황에서는 명시적인 접근성 처리가 필요합니다.

  • 커스텀 버튼·커스텀 토글 등 기본 위젯을 재구성한 경우
  • 이미지, 아이콘 버튼 등 시각적으로만 의미가 있는 요소
  • 복잡한 화면(카드, 리스트, 대시보드 등)의 요소 간 정보 구조를 정리할 필요가 있을 때
  • UI는 예쁘지만 VoiceOver 사용 시 정보가 뒤엉키거나 잘 전달되지 않는 경우

접근성 처리를 잘하면 다음 장점이 있습니다.

  • VoiceOver 사용자의 실제 사용성이 크게 향상
  • 테스트 자동화에서 UI 요소 식별이 쉬워짐
  • 국제 기준 WCAG 충족 → 서비스 품질 상승
  • iOS 생태계에서 “올바른 앱”으로 평가받음

SwiftUI는 .accessibilityLabel, .accessibilityValue, .accessibilityHint,

그리고 .accessibilityAddTraits, .accessibilityActions 같은 강력한 API를 제공합니다.

 

2. 잘못된 패턴 예시

❌ 예시 1: 아이콘 버튼에 접근성 레이블이 없음

Button {
    deleteItem()
} label: {
    Image(systemName: "trash")   // ❌ 시각적 의미만 있고 VoiceOver는 “휴지통 이미지”라고 읽음
}

문제점

  • VoiceOver는 이 버튼이 “삭제” 버튼인지 알 수 없다
  • 같은 화면에 아이콘 버튼이 여러 개 있으면 구분할 수 없음

❌ 예시 2: 카드 UI가 너무 많은 정보를 한 번에 읽음

struct WrongCard: View {
    let item: Item

    var body: some View {
        VStack(alignment: .leading) {
            Text(item.title)
            Text("금액: \(item.amount)")
            Text("날짜: \(item.dateString)")
        }
    }
}

문제점

  • 카드 전체가 하나의 덩어리로 읽히며 구조가 불명확
  • 제목, 금액, 날짜를 따로 탐색할 수 없어 사용성 저하

❌ 예시 3: 커스텀 토글을 만들었지만 접근성 동작을 제공하지 않음

struct CustomToggle: View {
    @Binding var isOn: Bool

    var body: some View {
        Circle()
            .fill(isOn ? .green : .gray)   // ❌ VoiceOver는 단순한 원으로 인식
            .onTapGesture {
                isOn.toggle()
            }
    }
}

문제점

  • VoiceOver 사용자는 이 UI가 “토글”이라는 사실을 모름
  • 더블탭 제스처로 토글을 변경할 수 없음
  • 상태(on/off)를 전달할 방법도 없음

 

3. 올바른 패턴 예시

✅ 예시 1: 아이콘 버튼에 명확한 레이블과 힌트 제공

Button {
    deleteItem()
} label: {
    Image(systemName: "trash")
}
.accessibilityLabel("항목 삭제")
.accessibilityHint("현재 선택된 항목을 삭제합니다")

장점

  • VoiceOver는 “항목 삭제, 버튼”으로 읽음
  • 기능이 명확하게 전달됨

✅ 예시 2: 카드 UI를 접근성 그룹으로 정돈

struct AccessibleCard: View {
    let item: Item

    var body: some View {
        VStack(alignment: .leading) {
            Text(item.title)
            Text("금액: \(item.amount)원")
            Text("날짜: \(item.dateString)")
        }
        .accessibilityElement(children: .combine)
        .accessibilityLabel("\(item.title), 금액 \(item.amount)원, 날짜 \(item.dateString)")
    }
}

장점

  • 카드 전체를 하나의 요소로 읽되, 구조적 문장으로 변환
  • 리스트 탐색이 훨씬 자연스러움

✅ 예시 3: 커스텀 토글에 접근성 동작과 값 제공

struct AccessibleToggle: View {
    @Binding var isOn: Bool

    var body: some View {
        Circle()
            .fill(isOn ? .green : .gray)
            .frame(width: 40, height: 40)
            .accessibilityLabel("알림 설정")
            .accessibilityValue(isOn ? "켜짐" : "꺼짐")
            .accessibilityAddTraits(.isButton)
            .accessibilityAction {
                isOn.toggle()       // VoiceOver 더블탭으로 토글 가능
            }
            .onTapGesture {
                isOn.toggle()
            }
    }
}

장점

  • VoiceOver가 “알림 설정, 켜짐/꺼짐, 버튼”으로 읽어줌
  • 접근성 사용자도 동일한 기능을 수행 가능
  • 커스텀 UI도 기본 토글처럼 동작

✅ 예시 4: Dynamic Type(폰트 크기 확대) 대응

Text("설정")
    .font(.title2)
    .dynamicTypeSize(..<DynamicTypeSize.accessibility3)

장점

  • 일반 사용자와 접근성 사용자 모두 가독성 향상
  • UI가 깨지지 않도록 확대 제한을 둘 수도 있음

 

4. 실전 적용 팁

✔ 팁 1 – 아이콘 버튼에는 반드시 Label을 붙인다

UI가 예뻐도 VoiceOver는 “이미지”라고만 읽는다.

✔ 팁 2 – 커스텀 UI는 ‘Accessibility Action’을 직접 지정

더블탭으로 실행 가능한 행동을 제공해야 한다.

✔ 팁 3 – 카드형 UI는 “combine” 또는 “ignore” 를 적절히 사용

정보가 너무 많으면 combine으로 요약해서 읽히도록 조정.

✔ 팁 4 – Dynamic Type 대응은 접근성의 핵심

폰트 크기를 제한하지 말고 UI가 유연하도록 설계.

✔ 팁 5 – 프리뷰에서 VoiceOver 시뮬레이션

Xcode Accessibility Inspector 또는 iOS 기능으로 확인 가능.

 

5. 정리

  • SwiftUI는 접근성 API를 풍부하게 제공하지만,
    커스텀 UI에서는 반드시 명시적으로 Label·Value·Hint·Actions를 설정해야 한다.
  • 접근성을 설계하면 시각장애인을 위한 기능뿐만 아니라,
    테스트 자동화·국제화·UI 구조 정리까지 자연스럽게 개선된다.
  • 앱의 완성도는 “보이기만 예쁜 UI”가 아니라
    “모든 사용자가 사용할 수 있는 UI”에서 결정된다.
반응형
Posted by 까칠코더
,