Hacktober 2017 Log #1 – Improving Accessibility

Hacktober started again! Hacktober is an event hosted by Github and DigitalOcean to promote contributing to open source. And for this year, I decided on a theme; improving accessibility. And to keep track of what I learned, I’ll be maintaining a log here.

Improving Accessibility

I want to do my part in making apps and technology more accessible to everyone. So I want to get more practice in finding accessibility issues and fixing those.

I’m also a frequent attendee of the CocoaHeadsNL meetup. They have an open source app, which lists all the events and notifies you when a new one is posted. So it’s a good way to quickly RSVP for a meetup. However, accessibility was not a main concern when developing this. So after a quick audit, I came upon the following two issues that could be fixed quickly;

Accessibility tasks for this screen

  • All the main screens have the CocoaHeadsNL banner image as their title view. Fine for people with proper vision, but a user with VoiceOver would have little idea what was going on.
  • The event table view cells would probably benefit from some accessibility improvements too. They’re usually a mess.

Improving the title’s VoiceOver

If you enable VoiceOver, the title of the Events screen sounded as follows;

CocoaHeadsNL. Header. Image.

This doesn’t make things clear at all. So my first attempt at fixing this, looked like this;

// This failed
let imageView = UIImageView(image: UIImage(named: "Banner"))
imageView.accessibilityLabel = NSLocalizedString("Upcoming and past events")
self.navigationItem.titleView = imageView

However, that didn’t actually change the VoiceOver message? So what happened?

Instead of using the imageView’s accessibilityLabel, VoiceOver uses the view controller’s navigationItem.title to determine what it reads. And that was set in Storyboard! Mystery solved. So my new code looks like this.

let imageView = UIImageView(image: UIImage(named: "Banner"))
self.navigationItem.titleView = imageView
self.navigationItem.title = NSLocalizedString("Upcoming and past events")

Okay, now what does VoiceOver say?

Upcoming and past events. Header. Image.

That’s good. However, the “Image” trait doesn’t work well. Someone with VoiceOver doesn’t really care that it’s an image. So we remove that UIAccessibilityTrait. However, removing this somehow also removed the “Heading” trait (no idea why), which actually is useful. So we add that again. Our final piece of code looks like this;

let imageView = UIImageView(image: UIImage(named: "Banner"))
imageView.accessibilityTraits = imageView.accessibilityTraits & ~UIAccessibilityTraitImage
imageView.accessibilityTraits = imageView.accessibilityTraits | UIAccessibilityTraitHeader

self.navigationItem.titleView = imageView
self.navigationItem.title = NSLocalizedString("Upcoming and past events")

The bitwise operators first remove the image trait, regardless of wether it was set or not. Then we add the header trait, regardless of whether it was set or not.

Done! Our first improvement is on the way.

Improving the event cell’s VoiceOver

When I audited the event table view cells, I was actually very pleasantly surprised.

June. Eleven. WWDC 2016 Keynote. Amsterdam 7:00 PM. 89 cocoaheads had a blast.

Well, that is nearly perfect, particularly the pronounciation of the month was unexpected. So why is it perfect? The main reason is that a UITableViewCell plays very well with VoiceOver. As you can see, the order in which it speaks coincides with the order the view’s are arranged in the cell’s contentView.

Meetup cell's content view arrangement; month, date, title, time and location, rsvp number

However, something weird showed up. While every month seemed fine in VoiceOver (even “Jan” was pronounced as “January”), only the month March didn’t participate. “Mar. 30.”. No idea why that happens. But instead of contemplating it too long, I decided to force the issue;

let date = Date()
dateFormatter.dateFormat = "MMM" // Shortened month name, i.e. 'mar'
monthLabel.text = dateFormatter.string(from: date).uppercased()
dateFormatter.dateFormat = "MMMM" // Full month name, i.e. 'march'
monthLabel.accessibilityLabel = dateFormatter.string(from: date)


Wrapping up

Both improvements went into this pull request which has been merged already! 🎉

Lessons learned:

  • A navigationItem’s title takes precedance for VoiceOver.
  • Some traits from the navigationItem.titleView trickle down to VoiceOver as well.
  • VoiceOver has some magic that translates shortened month names to full month names (i.e. “Jan” becomes “January”).
    • However, somehow this doesn’t work for March (i.e. “Mar” stays like that).
  • Refreshed my knowledge on bitwise operators.

If you have a comment or a suggestion, send me a tweet or a toot!