iOS 네이티브에서 접근성 초점 캐치하기
안녕하세요, 엔비전스입니다.
iOS 접근성을 구현하다 보면 VoiceOver가 특정 요소가 초점을 갖고 있거나, 초점을 잃었는지, 값으로 얻어야 할 때가 있습니다. 예를 들면, 사용자가 롤링 중인 배너에 포커스 했을 때, 배너를 정지시키거나 화면 전체를 다시 불러와야 할 때, VoiceOver가 초점을 가진 요소만 제외해야 할 때 등입니다. 이때 사용할 수 있는 접근성 API가 바로 UIAccessibilityFocus입니다. 본 문서에서는 앞에서 예로 든 두 가지 사례를 통하여 UIAccessibilityFocus API 사용방법에 대해 살펴보도록 하겠습니다.
사례 1: VoiceOver 포커스 캐치하여 배너 정지시키기
iOS에서 배너의 롤링 효과를 구현하는 방법은 크게 두 가지로 나눌 수 있습니다.
첫 번째로 배너를 감싸고 있는 superView(상위 컨테이너) 내에 여러 배너를 두고, 배너 항목이 스크롤 되면서 롤링이 되는 방식입니다. 이때, 접근성 적용을 하지 않으면 모든 배너 항목이 별개의 객체로 각자 초점을 갖습니다. 즉 배너 10개가 롤링이 되고 있다면 VoiceOver 사용자는 10개의 배너를 모두 탐색하게 된다는 것입니다.
두 번째 방법은 여러 개의 배너 항목을 별도로 두지 않고, 단 하나의 객체에 여러 줄로 나누어 각 줄에 배너 항목을 입력하고 배너로 사용 중인 뷰의 높이를 텍스트 한 줄의 높이로 지정을 한 다음, 배너가 자동으로 스크롤 될 때마다 눈에 보이는 높이만큼만 스크롤을 조절하는 방법입니다.
이런 경우에는 시각적으로는 배너가 하나씩 회전하는 것처럼 보이지만, VoiceOver는 단 하나의 요소에 여러 개의 배너 항목이 텍스트 형태로 들어가 있으므로 해당 배너의 모든 항목을 한꺼번에 읽어버리는 문제가 생깁니다. 즉 사과, 귤, 바나나와 같은 배너 항목이 롤링이 되고 있다면 VoiceOver에서는 해당 배너에 초점을 보냈을 때 사과, 귤, 바나나를 한꺼번에 읽게 된다는 의미입니다.
따라서 위의 두 가지 중 어떤 경우이든 배너의 접근성을 개선하기 위해서는 다음이 충족되어야 합니다.
- 배너의 텍스트가 들어 있는 상위 ScrollView 혹은 CollectionView 등을 커스텀 클래스로 지정합니다. 예: class BannerView: UIScrollView
- 접근성 초점을 해당 클래스 자체에 줍니다. 예: self.isAccessibilityElement = true
- 화면에 표시되는 배너 텍스트를 accessibilityLabel을 통해서 실시간으로 업데이트합니다. 예: self.accessibilityLabel = numberArray[nowPage]
- 각 배너를 실행할 수 있거나 혹은 이전 다음 배너로 넘기는 기능을 구현하는 경우에는 accessibilityTraits = [.button, .adjustable] 로 지정합니다.
- accessibilityIncrement, decrement 메서드를 override하여 이전 혹은 다음 배너로 넘기는 기능을 구현합니다. accessibilityIncrement, decrement 구현 방법에 대해서는 관련 아티클을 참고합니다.
위에서 언급한 것들은 배너 접근성 구현을 위한 기본적인 방법이며 오늘 여기서 살펴보고자 하는 것은 VoiceOver 초점이 배너가 있는 요소에 머물러 있을 때 배너를 멈추게 하는 것입니다. 이때 사용할 수 있는 메서드가 accessibilityElementDidBecomeFocused(), accessibilityElementDidLoseFocus() 입니다.
코드 문구에서도 알 수 있듯이 didBecomeFocused는 해당 클래스에 접근성 초점이 접근했을 때이고 loseFocus는 초점을 벗어날 때입니다. 따라서 위에서 예로 든 bannerView 스크롤 뷰로 생각해 보자면, 해당 클래스 자체에 접근성 초점을 주었으므로 VoiceOver 초점이 해당 배너에 접근했을 때 didBecomeFocused 상태가 되므로 이때는 배너 정지, 포커스를 잃었을 때는 배너 재생 메서드를 추가하는 형식으로 구현을 할 수 있겠습니다.
사례 2: VoiceOver 초점 캐치하여 데이터 재로드하기
VoiceOver로 접근성 테스트를 하다 보면 테이블 뷰에서 특정 요소를 선택했을 때 데이터가 다시 불러오면서 접근성 초점이 유지되지 못하고 다른 요소로 튕기는 경험을 한 적이 꽤 있을 것입니다. 일반적인 탭 막대나 semanticContorls 와 같은 객체들은 이중 탭을 하여 특정 요소를 선택할 때 전체적으로 데이터가 다시 불러오기는 하지만 선택하는 컨트롤 자체가 포함된 영역은 정적인 영역이기 때문에 선택을 했다고 해서 VoiceOver가 초점을 튀지 않으며 선택한 해당 요소에 머물러 있습니다.
그러나 테이블 뷰 내에서 무언가를 선택하는 구조인 경우 화면에는 로딩 중이라는 문구를 표시하면서 테이블 뷰 자체의 데이터 전체를 다시 구성하는 경우가 종종 있습니다. 이럴 때, VoiceOver가 머무르고 있는 초점 자체가 사라졌다가 다시 표시되므로 VoiceOver 초점이 선택한 요소에 머무르지 못하고 근접한 다른 요소로 튀는 경우가 많은데, 이것은 VoiceOver 사용자에게 일관된 경험을 주지 못하므로 개선이 필요한 부분입니다.
가장 좋은 것은 처음부터 데이터 재구성을 할 때 선택한 요소는 재구성 영역에서 제외하고 그대로 두는 것이지만 다음과 같은 이유로 일반적으로 그 방법을 적용하기에는 어려움이 있을 수 있습니다.
- 서버에서 데이터를 다시 가지고 오는 동안 사용자가 선택한 요소를 다시 탭하여 문제가 발생할 수 있음.
- 데이터를 다시 불러올 때, 특정 애니메이션 효과를 주고 싶을 수 있음.
따라서 이럴 때에도 VoiceOver 초점을 캐치하여 선택 요소에 초점을 두고 있는 동안에는 초점을 가진 요소는 제외하고 다시 불러오도록 하도록 구현하여 해당 문제를 해결할 수 있습니다.
예를 들어 테이블 뷰 내에 2개의 셀이 있으며, 첫 번째 셀과 두 번째 셀에는 각각 5개와 10개의 컬렉션 뷰가 있다고 생각해 봅시다.
첫 번째 테이블 뷰 셀은 탭 막대(이하 탭 막대 셀)이며, 1번부터 5번 셀은 각각의 탭이며, 두 번째 테이블 뷰 셀은 콘텐츠 표시 영역(이하 콘텐츠 셀)이며, 6번부터 15번까지의 컬렉션 뷰가 있습니다.
탭 막대 셀에서 5개 중 하나의 탭을 선택하면, 콘텐츠 셀에 있는 6번부터 15번까지 있는 컬렉션 뷰 셀에 선택한 탭에 대한 데이터를 표시합니다. 기본적으로는 어떤 셀을 선택하더라도 데이터 전체가 다시 불러올 것이지만 VoiceOver 초점이 탭 막대 셀 중, 한곳에 있으면 해당 셀은 제외하고 다시 불러오는 것입니다. 해당 코드에 대한 예시는 널리 포럼 팁을 참고합니다.
지금까지 VoiceOver 초점을 캐치하여 할 수 있는 사례에 대해서 살펴보았습니다. 이러한 접근성 적용 방법들이 조금 더 널리 알려지길 바라봅니다.