LoginSignup
0
0

More than 3 years have passed since last update.

I Made My DIY Bar Chart Accessible using UIAccessibility in Swift

Posted at

[Wrote in Swift 5 with Xcode 10]

The App

I’m not a professional programmer. I do coding for a hobby. But when I code, I always tried to make it accessible as possible.

Currently I’m creating an app which adds a bit of a fun to board game playing. I have been used it myself in closed settings for a while and, IMHO, it worked exceptionally well so far. So I thought it might be a good idea to share this tiny, humble app with a fellow board gamers around the world through the App Store!!

But before I do that, I need to make it accessible as far as I could go.

What's the App for?

Basically, It is a dice counter. A player rolls dice and records that dice number with the app. The App stores and shows those results using collection view and bar graphs. That's it.

It's a simple app and it has a straight forward UI. So without taking much effort, thanks to the Apple’s great UIAccessibility features, it already is a decent accessible app from the get-go. I’m not saying it is perfect or even great but decent enough just a tiny tweak would suffice to cut it.

Except for a bar chart.

DIY Stacked Bar Chart

About a Chart with Animation

(I wrote about this chart itself extensibly here and here. Unfortunately those are Japanese articles but the code itself is an universal language so I hope you will get the idea.)

Because I wrote it by myself I know every bits how it works. It helped me a lot when I planned how I should adapt UIAccessibility and it took me a little to no effort when I implemented it. Here’s my thought process of it.

What happened if I didn’t do anything.

For the beginning, let's see what would it look like without any works.

AccessibleBarChart-Before.gif

This gif shows how a focus of VoiceOver moved between views. I'm not sure why but the system has never bothered to call into Bar itself. Y axis labels without actual values are useless to say the least.

So how should I improve it? Let's find out!

Recommended Watch

If you are totally new to the topic, I recommend you to watch this 2015 WWDC session. Yes it is old (4 fracking years ago!!) but if you want to know very basics of the framework, this is the session you want to watch.

isAccessibilityElement

We need to tell the system that whether the object is supposed to be accessible or not. For example, if it exists just because for visual clarity reason it doesn't make sense it is accessible. "This line is a separator of two views" won't help user for anything. It's just a noise. So we should be set isAccessibilityElement of those parts as false.

On the other hand, Bar needs to be accessible because it holds a meaningful information. So make them so accordingly.

Bar.swift
override var isAccessibilityElement: Bool {
  get {
    return true
  }
  set { }
}
ChartController.swift
yScaleLabel.isAccessibilityElement = false

accessibilityLabel

A label is a name of the view. What would be an appropriate name for the bar? I'm not sure about this (because my English sucks, as you've already realized), but I would go like this

"Bar graph for (number of X axis)"


override var accessibilityLabel: String? {
  get {
    return "Bar graph for \(tag)"
  }
  set { }
}

Let me know if you have better idea. Better wording, etc.

accessibilityValue

Next stop is value. This is the fun part.

So what is a value of the stacked bar graph?

I'm not an expert of a stacked bar chart so I might totally miss the point but I guess what user should get here is a value of the each "stack" of graph and total sum of them.

e.g.) "Red 1, Blue 2, Total 3"

Based on this idea, I came up with some rules.

  1. Just return colors which have values. (ignore ones with no value)
  2. If it has zero value then it should not be accessible.
  3. Considering localization, it should have a flexibility in wording.

Let's implement these.

Create Source for Accessible Data.

It would be handy if we had a dedicated data type for the bar graph.

struct AccessibleData<T: LosslessStringConvertible> {
    /// value. Int, in this case
    var value: T
    /// label would be color; red, blue etc..
    let label: String
    /// set closure which takes value and label
    let descriptor: (String, T) -> String

    func description() -> String {
        return descriptor(label, value)
    }
}

protocol AccessibleChartDataSource {
    var accessibleValues: [AccessibleData<Int>] { get }
}

And use these like this.

let accessibleData = AccessibleData(
                         value: tempAccessibleValues[idx],
                         label: UIColor(cgColor: playerCGColor).toString, 
                         /// "Red 1", "Blue 2"
                         descriptor: { $0 + " \($1)" }
                         )

I extended UIColor to return color name as a string.

Next, comfort Bar with AccessibleChartDataSource.

Bar.swift
class Bar: UIView, AccessibleChartDataSource {
    var accessibleValues = [AccessibleData<Int>]()
    ...

After that, we need to update isAccessibilityElement as it reflects its own state.

Bar.swift
override var isAccessibilityElement: Bool {
  get {
    let totalValue  = accessibleValues.reduce(0, { $0 + $1.value })
    return totalValue > 0 ? true : false
  }
  set { }
}

And finally, accessibilityValue.

Bar.swift
override var accessibilityValue: String? {
    get {
        /// It should goes along something those lines;
        /// jpn: "赤2 青1 で合計3"
        /// eng: "Red 2, blue 1, total 3 "
        let totalValue  = accessibleValues.reduce(0, { $0 + $1.value })
        var description = accessibleValues.reduce("", { result, data in
            if data.value > 0 {
                if result.isEmpty {
                    return data.description()
                }
                else {
                    return result + ", " + data.description()
                }
            }
            return result
        })
        description += ", total \(totalValue)"
        return description
    }
    set {   }
}

Don't forget to update value when graph's value has changed.

What about other properties?

We are not touching these properties this time because I don't think we need to, but they deserve to be considered if it helps users to interact with your app.

var accessibilityTraits: UIAccessibilityTraits
var accessibilityHint: String?
var accessibilityFrame: CGFrame

I pondered if charts are UIAccessibilityTraits.image) or not for a while but after I tried it on the device I don't think it is a good idea.

Conclusion

After these changes, Accessibility Inspector shows us now it returns much more appropriate information!

BarChartAccessibility-After.gif

Thank you for reading!

Diagram of the Chart

FYI, a diagram of the chart is like this.

ChartController
                                                     ┌──────────────────┐                               
                                                     │    Storyboard    │                               
                                                     └──────────────────┘                               
                                                           @IBOutlet                                    
ChartController────────────────────────────────────────────────┼───────────────────────────────────────┐
│                                                              ▼                                       │
│                   ┌───────────────────────┐        ┌──────────────────┐                              │
│                   │        UILabel        │   ┌────│    ChartView     │                              │
│                   │     (yAxisLabel)      │◀──┘    └──────────────────┘                              │
│                   └───────────────────────┘                  │                                       │
│                                                              ▼                                       │
│                                                  ┌───────────────────────┐                           │
│                                                  │      UIStackView      │                           │
│                                                  │    (barStackView)     │                           │
│                                                  └───────────────────────┘                           │
│                                                      arrangedSubviews                                │
│                                  ┌───────────────────────────┼───────────────────────────┐           │
│                                  ▼                           ▼                           ▼           │
│                        ┌──────────────────┐        ┌──────────────────┐        ┌──────────────────┐  │
│                        │       Bar        │        │       Bar        │        │       Bar        │  │
│                        └──────────────────┘        └──────────────────┘        └──────────────────┘  │
│                                                              │                                       │
│                                           ┌──────────────────┴────────────────┐                      │
│                                           ▼                                   ▼ subview              │
│                               ┌───────────────────────┐           ┌───────────────────────┐          │
│                               │        CALayer        │           │        UILabel        │          │
│                               └───────────────────────┘           │     (xAxisLabel)      │          │
│                                       sublayers                   └───────────────────────┘          │
│            ┌──────────────────────────────┼─────────────────────────────┐                            │
│            ▼                              ▼                             ▼                            │
│┌───────────────────────┐      ┌───────────────────────┐     ┌───────────────────────┐                │
││    ColorStackLayer    │      │    ColorStackLayer    │     │    ColorStackLayer    │                │
││       (CALayer)       │      │       (CALayer)       │     │       (CALayer)       │                │
│└───────────────────────┘      └───────────────────────┘     └───────────────────────┘                │
└──────────────────────────────────────────────────────────────────────────────────────────────────────┘   
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0