iyOmSd/Title: Swift

[Swift] WWDC21 Bring accessibility to charts in your app

냄수 2021. 12. 8. 18:23
반응형

최근에 접근성에 대한 글을 보곤 해본적이 없어서 호기심에 하나씩 세션을 들어보려고해요!

 

 

이번 세션에서 다룰 목차입니다

차트에 접근 가능할 수 있게함으로서 얻을 수 있는 이점

시각적 측면에 대한 포괄적 결정을 위한 몇가지 원칙

VoiceOver을 통해 차트를 인식 및 탐색하는 방법

오디오 그래프 기능

 

---

 

차트는 세부사항에 깊이 접근할 필요 없이 데이터가 무엇을 말하는지 빠르게 이해할 수 있기 때문에 유용합니다

그러나 차트는 시각장애인이나 시력이 낮은 사람들이 본질적으로 접근할 수 있는것은 아닙니다.

보이지 않으면 시각차트는 가치가 없습니다.

 

오디오그래프를 지원해서

 

차트의 상세를 볼 수 있도록 음성 지원이 나타나고

차트그래프를 신호음으로 높낮이를 보여주고

차트의 전체뿐 만아니라 부분만 집중해서 소리가 나오게 할 수도 있습니다.

(영상으로 보여주는데 말로설명하기가 어렵네요..ㅠ)

 

차트에 엑세스 할 수 있도록 설정하면 데이터가 풍부한 애플리케이션에서 시각장애인 및 저시각 사용자를 포함한 더 많은 사용자에게 접근 할 수 있다

차트데이터를 이해하는데 유용한 도구를 제공함으로써 사용자에게 권한을 부여하여 개인 및 직장생활에서 데이터의 놀라운 기능을 활용 할 수 있도록 지원합니다.

올해 새로운 API가 출시되면서 차트를 만드는것이 그 어느때보다 쉬워졌습니다

 

오디오 그래프를 지원하기전에

몇가지 일반적인 차트 접근성 주제에 대해 살펴봐야합니다.

차트를 시각적으로 보다 쉽게 접근 할 수 있는 방법 부터 보겠습니다.

 

시각 접근성을 개선하기위해 몇가지 접근성 원칙을 적용하겠습니다,.

첫째, 가능한 한 고대비 색을 사용하는 것이 중요합니다.

전경색과 배경색의 대비가 높아 차트를 쉽게 보고 이해할 수 있으며 특히, 시력이 낮은 사람들이 그렇습니다.

 

선과 배경의 대비가 상당히 낮고

제목과 레이블 텍스트도 살짝 흐릿해보입니다.

이색상들을 배경 대비로 업데이트 해보겠습니다.

훨씬 좋게 보입니다

 

색상간 대비비율을 확인하여 대비가 충분한지 여부를 확인 할 수 있습니다.

Xcode와 함께 제공되는 색대비 계산기가 있습니다.

최소한 4.5:1의 대비율을 목표로 하는 것이 좋습니다.

 

 

다음으로 시각적 접근성을 위해 할 수 있는 것으로는

문제가 되는 색상 쌍을 피하는 것입니다.

가능한 빨간색과 녹색을 함께 사용하지 않는 것이 일반적으로 좋은 관행입니다.

 

빨간색과 녹색 색맹이 가장 흔한 유형이고 이 색상을 함께 사용하는것은 사람들에게 차트를 이해하기 어렵게 만들 수 있습니다.

만약 여러분이 적녹색 색맹이라면 그차트는 여러분들에게 이것과 비슷해 보일것입니다.

어떤것이 건조한 강우인지 열대성 강우인지 구분하기 어려울것입니다.

 

빨간색은 파란색으로 대체해서 개선합시다

왼쪽: 보통사람               오른쪽: 적녹색맹

이것은 큰 변화처럼 보이지 않을 수 있지만 적녹색맹이 있는 사람들에게는 이렇게 보일 수 있습니다.

 

또한 파란색과 노란색을 함께 사용한는 것도 피하면 좋은데,

이 색맹계열이 두번째로 흔하기 때문입니다.

 

데이터 열을 구분하기위해 색상과 기호를 함께 사용해서 발전시켜보겠습니다.

데이터기호를 사용하면 사람들이 색상에 의존하지 않고도 차트를 이해 할 수 있습니다.

연관된 기호만 봐도 어떤 차트인지 알 수 있습니다.

 

 

 내게 필요한 옵션에서 옵션이 설정된 경우를 구분해서 Accessibility한 환경을 제공 할 수 있습니다.

 

VoiceOver사용자들이 데이터를 탐색할 수 있는 방법을 알아보겠습니다.

첫번째 방법으로 차트를 컨테이너로 만드는 방법이 있습니다.

VoiceOver가 차트요소를 올바르게 그룹화하고 네비게이션에 도움이 됩니다.

차트를 컨테이너로 만드려면 accessibilityContainerType 재정의 해야합니다.

accessibilityContainerType을 sematicGroup으로 반환합니다.

이는 VoiceOver 탐색 및 차트에 속한 요소 간 전달에 중요합니다.

 

다음으로 차트에 accessibilityLabel을 제공해야합니다.

차트를 볼때 VoiceOver에게 말해야 할 내용을 알려줍니다.

일반적으로 차트의 제목이거나 UI에서 차트를 고유하게 식별하기 위해 VoiceOver가 말할 수 있는 유사한 내용이여야합니다.

 

마지막으로 각 데이터 지점에 대한 accessibilityElements을 제공해야합니다.

차트뷰의 accessibilityElements속성을 재정의하여 이러한 요소를 제공 할 수 있습니다.

이렇게 하면 차트내에 VoiceOver가 탐색할 수 있는 요소가 생성되어 사람들이 개별 데이터 포인트에 대한 정보를 얻을 수 있습니다.

이기능을 구현하려면 map을 이용해서 하면됩니다.

 

각 데이터 지점에 대해 새로운 accessibilityElement 객체를 만듭니다.

accessibilityValue속성에 데이터 포인트의 문자열 표현을 제공합니다.

이 문자열은 VoiceOver가 데이터포인트 요소를 탐색할 때 말하는 문자열입니다.

 

마지막으로 VoiceOver가 화면 어디에 있는지 알 수 있도록 요소에대한 accessibilityframe을 제공해야합니다.

accessibilityLabel을 제공해서 차트 내부요소를 focus할때 타이틀을 말합니다.

(VoiceOver가 넣어준 Label인 model의 title을 읽게 됩니다.)

또한, 각 데이터 지점을 나타내는 UIAccessibilityElement를 만들어줘서 각 지점을 탐색할 수 있도록 했습니다.

 

VoiceOver: Cups of coffee versus lines of code.
Zero cups. Twenty lines of code.
One cup. Thirty lines of code.
Two cups. Thirty-five lines of code.
Three cups. Thirty-two lines of code.

 

넘어가기전에

때로는 수백, 수천개의 데이터 지점이 있을 수 있습니다.

이러한경우 탐색해야할 항목이 너무 많기때문에 만들지 않는것이 좋습니다.

대신 차트를 합리적인 간격으로 나누고 각 데이터 지점보다는 각 간격에 대한 accessibility element를 만드는것을 권장합니다.

이렇게하면 더 나은 경험을 제공할 뿐만아니라 여전히 이해할 수 있는 성능을 향상시킬 수 있습니다.

 

아직 끝이 아닙니다.

 

데이터를 탐색할 수 있도록하는 것이 중요하지만

VoiceOver사용자에게 원시데이터를 한번에 하나 씩만 제공하고있습니다.

차트의 가치는 데이터가 실제로 무엇을 말해주고 있는지 보여주고 개별 데이터 지점을 넘어 설 수 있도록 도와주는 기능입니다.

여기서 오디오 그래프가 나옵니다.

 

모델에서보면

제목과 요약이있고

x,y축이 있고

축의 표시가능한 최소,최대범위가 있습니다.

마지막으로 차트의 실제데이터를 포함하는 데이터포인트 배열이 있습니다.

이 모델 객체의 정보를 사용하여 차트의 오디오 그래프 기능을 활성화합니다.

 

import Accessibility를 한다음

AXChart 프로토콜을 준수합니다

accessibilityChartDescriptor 프로퍼티만 구현하면 되기때문에 쉽습니다.

VoiceOver가 오디오그래프 환경을 제공하는데 필요한 모든 정보가 포함됩니다.

 

먼저 각 축에대한 축 설명자 객체를 만들어야 합니다.

축설명자는 VoiceOver에 숫자형데이터, 범주형데이터의 여부, 축에대한 표시가능한 범위, 그리드선의 위치 및 어떻게 데이터의 값을 소리낼지 형식에 대한 정보를 제공합니다.

커피 차트에서 X축이 숫자이므로 숫자축 설명자를 생성하겠습니다.

범주형 데이터가 있다면 AXNumericDataAxisDescriptor가아닌 AXCategoricalDataAxisDescriptor를 사용할 것입니다.

gridline을 제공한다면 재생및 상호작용중 햅틱피드백으로 오디오 그래프에 표시됩니다.

이차트는 X축 격자선이 없기때문에 비워두겠습니다.

숫자 축인경우 valueDescriptionProvider클로져도 생성해야합니다.

VoiceOver에게 축의값을 말하는 방법을 알려주기때문에 아주 중요합니다.

 

Y축에대한 정보도 X축과 같이 작업을 수행합니다.

값설명을 cups대신 lines of code로만 변경하겠습니다.

이제 차트의 기본구조가 정의되었으므로 데이터를 추가하겠습니다.

 

각 data series에 대해 data series설명자를 추가합니다.

(여기서 말하는 series는 차트의 다른지표, 다른 데이터의 표시를 말하는 듯합니다!)

이 차트에는 data series가 하나만 있으므로 설명자 하나만 생성하여 열 이름, 연속 여부 및 이 series에 대한 실제 데이터 지점을 전달합니다.

 

series가 점이나 막대와 같이 불연속적인 차트라면 isContinuous에 false를 전달해야합니다.

선으로 시각적으로 표현될때는 true를 전달합니다.

 

series데이터의 경우 

AXDataPoint객체 배열을 제공해야합니다.

 

그리고 AXChartDescriptor객체를 만들기위해 조각들은 모두 합칩니다.

title을 줄 것입니다.

차트의 제목이 될 것입니다.

 

제목이있다면 차트의 요약을 제공할 수 있습니다.

이 속성을 이용해서 데이터에서 가장 중요한 통찰력을 한두문장으로 전달해야합니다.

이 요약텍스트는 오디오 그래프 탐색기 보기의 사용자에게 제공됩니다.

이것은 당신의 차트를 이해하려고 노력하는 VoiceOver사용자들에게 믿을 수 없을 정도로 도움이 될 수 있습니다.

 

마지막으로 앞서 작성한

축정보 및 data series설명자를 chart descriptor에 제공하여 완성한 후 반환할 것입니다.

끝입니다!

 

 

 

직접구현해볼게요!

 

우선 사용하는 방법은

환경설정에 VoiceOver를 키기만하면 알아서 적용되구요

 

 

class ViewController: UIViewController {
    var chartView: ChartView?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let model = ChartModel(title: "타이틀",
                               summary: "요약",
                               xAxis: .init(title: "x축", range: 0...100),
                               yAxis: .init(title: "y축", range: -100...100),
                               dataPoints: [ChartModel.DataPoint(name: "1번", x: 10, y: 1),
                                            ChartModel.DataPoint(name: "2번", x: 50, y: 10),
                                            ChartModel.DataPoint(name: "3번", x: 20, y: 20),
                                            ChartModel.DataPoint(name: "4번", x: 40, y: 30),
                                            ChartModel.DataPoint(name: "5번", x: 10, y: 100)])
        chartView = ChartView(model: model)
        view.addSubview(chartView!)
        chartView?.translatesAutoresizingMaskIntoConstraints = false
        chartView?.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
        chartView?.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
        chartView?.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
        chartView?.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
        
        chartView?.drawChart()
    }
}


class ChartView: UIView {
    let model: ChartModel
    
    init(model: ChartModel) {
        self.model = model
        super.init(frame: .zero)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func drawChart() {
        model.dataPoints.forEach {
            let circle = UIView(frame: .init(x: $0.x * 5, y: $0.y * 5, width: 10, height: 10))
            circle.backgroundColor = .blue
            circle.layer.cornerRadius = 5
            addSubview(circle)
        }
    }
}

위에서 만든 차트뷰를 간단하게 점으로 찍어서 나타내볼거에요

 

그려줄 차트의 크기는 가로10 세로10의 점으로 찍을거고

그냥하면 다다다닥 붙어있을수 있기때문에 좌표에 곱하기 5를해줘서 좀 떨어지게 해줄거에요

 

이 frame을

아래의 함수를 정의하지않았는데

    private func frameRect(for dataPoint: ChartModel.DataPoint) -> CGRect {
        // 보여줄 프레임을 계산하는 로직을 넣어주세요
        return .init(x: dataPoint.x * 5, y: dataPoint.y * 5, width: 10, height: 10)
    }

 

이젠 적용해줄거에요

 

위와같이 점이 찍혀잇을거고

점을 클릭하면

frameRect에 정의한 크기대로 흰색 네모박스가 뜨면서 설정해준 텍스틍의 소리가 들리는걸 확인 할 수 있어요

 

 

 

반응형