Implementing Accessibility for a Custom Control in the Wikipedia iOS App

At the Wikimedia Foundation, the home of Wikipedia, we care a lot about accessibility.

If you read the Foundation’s vision statement, you can see why:

Imagine a world in which every single human being can freely share in the sum of all knowledge. That’s our commitment.

“Every single human being” means a lot of different people: different languages, different cultures, different internet connections, and different abilities.

And for us, this means our app needs to be accessible.

In keeping with this vision, we have been doing a significant amount of Accessibility development. Even so, there are many parts of the app that still need work.

And personally, I have been learning that actually implementing Accessibility is hard™.

Apple’s platforms make it easier, but even with all the great APIs and hardware support, implementing Accessibility is not always straightforward in our brave new world of interactive, dynamic, gesture driven interfaces.

Essentially, you must design a completely new interface for your app. And, if you consider how much thought and planning that would take, you will have a pretty good idea of what implementing Accessibility really involves.

This past week, I have been implementing a simple slider for re-sizing the text of our articles (one of our most user user requested features). We decided on a design using a popover with stepped slider:

Design mockup for a text size slider

Unfortunately, the built-in UIKit slider does not allow discrete steps like this. (Well that’s not entirely true, there are some methods that get close)

I knew that I wanted a custom control for this feature. I know that I am about to lose a few of you here… but before I implement any custom control, I head over to CocoaPods to see what other developers have built. We are a pretty small team and I take the approach of “standing on the shoulders of giants” whenever it makes sense.

In this case I found a control that fit the bill, almost perfectly, and written in Swift (bonus 🎉): SWStepSlider

Now, it isn’t a particularly popular control, but it’s fairly well written (IMHO) and simple enough, so I don’t mind taking on the burden of maintaining this control in exchange for not having to write it from scratch.

Integrating it was pretty easy as expected. it embeeded inside of a popover with no issues, and emits events correctly.

But there it has a big problem.

If you enable VoiceOver (triple-click home like a good iOS developer), and try to use the slider — it does not work — at all. 😢

Now, I am pretty handy with accessibility labels and the like — you know the easy stuff — but I haven’t had the pleasure of implementing accessibility on a custom control. So I rolled up my sleeves and got to work…

First things first: lets check out the implementation. I won’t post the whole class inline (you can see it here), but lets look quickly at the key components.

First is the Slider Track:

public override func drawRect(rect: CGRect) {
super.drawRect(rect)

let ctx = UIGraphicsGetCurrentContext()

CGContextSaveGState(ctx)
// Draw ticks
CGContextSetFillColorWithColor(ctx, self.tickColor.CGColor)

for index in 0..<self.numberOfSteps {
let x = self.trackOffset + CGFloat(index) * self.stepWidth — 0.5 * self.tickWidth
let y = self.bounds.midY — 0.5 * self.tickHeight

// Clip the tick
let tickPath = UIBezierPath(rect: CGRect(x: x , y: y, width: self.tickWidth, height: self.tickHeight))

// Fill the tick
CGContextAddPath(ctx, tickPath.CGPath)
CGContextFillPath(ctx)
}
CGContextRestoreGState(ctx)
}

As you can see, this is pure drawRect. As far as UIKit is concerned, this track doesn’t “exist” — at least not as an object. So the first question is “How will model these ticks for Accessibility?”

Next lets look at the Slider Thumb:

private func commonInit() {
self.trackLayer.backgroundColor = self.trackColor.CGColor
self.layer.addSublayer(trackLayer)

self.thumbLayer.backgroundColor = UIColor.clearColor().CGColor
self.thumbLayer.fillColor = self.thumbFillColor.CGColor
self.thumbLayer.strokeColor = self.thumbStrokeColor.CGColor
self.thumbLayer.lineWidth = 0.5
self.thumbLayer.frame = CGRect(x: 0, y: 0, width: self.thumbDimension, height: self.thumbDimension)
self.thumbLayer.path = UIBezierPath(ovalInRect: self.thumbLayer.bounds).CGPath

// Shadow
self.thumbLayer.shadowOffset = CGSize(width: 0, height: 2)
self.thumbLayer.shadowColor = UIColor.blackColor().CGColor
self.thumbLayer.shadowOpacity = 0.3
self.thumbLayer.shadowRadius = 2
self.thumbLayer.contentsScale = UIScreen.mainScreen().scale

self.layer.addSublayer(self.thumbLayer)
}

Here, we are using a “CAShapeLayer”, a completely different drawing technology, which is still not a view, but at least it is an object. Now we have our second question, “How do I make the slider thumb visible to Accessibility?”

With these questions in hand… to the docs!

Apple’s documentation here is actually pretty good — once you figure out what we want is a “container” that holds accessibility elements. To create a container, we actually only have to implement three methods… but thats only half the story — These methods are asking for objects that implement the accessibility APIs.

At this point, there are a few paths you could take. I decided to use the purpose built UIAccessibilityElement to represent the slider thumb. You can set the properties of this object, and 💥 the thumb is now accessible.

One quick gotcha that is easy to breeze over in the docs is the frame property… the frame but be in the coordinates of the screen. The easiest way to do this is:

element.accessibilityFrame = [self convertRect:textFrame toView:nil];
I picked this tip up from an article on UseYourLoaf

Now back to our first question: “How will model these ticks for Accessibility?”

We know we have a slider. We know we have discreet values. How do VoiceOver users usually navigate this control? A slider clearly requires a gesture for normal operation, so there must be a built in mechanism to manipulate an on screen element.

In this respect the docs fail us a little bit. The Accessibility Programming Guide essentially mentions nothing. 😔

Going back to the headers, I poked around a bit and eventually found something interesting in the UIAccessibilityElement interface:

@property(nonatomic, assign) UIAccessibilityTraits accessibilityTraits

And looking at the UIAccessibilityTraits enum, we find what we are looking for:

UIAccessibilityTraitAdjustable
The accessibility element allows continuous adjustment through a range of values.
Use this trait to characterize an accessibility element that users can adjust in a continuous manner, such as a slider or a picker view. If you specify this trait on an accessibility element, you must also implement the accessibilityIncrement and accessibilityDecrement methods in the UIAccessibilityAction protocol.

To be fair, this is in the docs, just not in the Accessibility Programming Guide — It is in the View Controller Programming Guide.

Once again, it is a matter of implementing a few informal protocol methods. But after that, supporting incrementing and decrementing is fairly easy. You can see a gif of what this looks like using VoiceOver below:

Navigating the slider using only VoiceOver

You can also see the full source on Github — it’s still a little rough, but it is a good demonstration on how to support custom controls.

I still need to DRY the implementation up a bit and use the availability APIs since this functionality is iOS 9 only

As for the Wikipedia app, this feature is still in beta, but we should be shipping an update with this enhancement soon. If you would like to join our beta group, you can drop us a line here. Or even better, if you want to contribute to our codebase, you can open a pull request here.

Lastly, if you want to make your own app more accessible a great place to begin is by watching this WWDC video, or if you would like help getting started, there are some great consultants out there who can give you a hand.

Show your support

Clapping shows how much you appreciated Corey Floyd’s story.