Adding Voiceover To Carousel Components
In a forthcoming version of the BBC News app we will be introducing a new form of our story carousels. The carousel will be a flexible component that can appear anywhere in the app, including the landing page, to display video, picture or article content or, more commonly, inside articles offering users related content should they want to read around a topic further.
However, they are a visual component that are difficult to convey to voiceover users. We faced a particular challenge recently as we endeavoured to create a carousel of related articles within an article.
We identified two main voiceover accessibility points that need to be addressed with this component:
- The carousel should be easy to skip.
Just as non-voiceover users can choose to not scroll through the carousel, voiceover users also need this option, particularly where the carousel interrupts the flow of the article. The default behaviour on iOS is to step through every item in a collection view carousel, which may be tedious for a voice over user who just wishes to read the article.
2. The carousel needs to make sense.
Without the visual context, the carousel cards can be confusing; voiceover users need to know that items in the carousel are clickable. In this context this is particularly important because an accessibility label such as ‘How do the new lockdown restrictions affect me?’ does not indicate that the item is a link to another article.
Our first implementation was based on this WWDC video: https://developer.apple.com/videos/play/wwdc2018/230/
This method allows users to skip or scroll through the carousel. However, this approach was originally designed for adjusting values on a slider rather than interactable elements, which our carousels will contain, meaning we lose the crucial button context associated with the cards. The bespoke solutions we then considered are as follows.
Place a custom item in the rotor
The accessibility rotor can be used to navigate the carousel, but the voiceover controls default is to skip it. The custom action in the rotor is announced when users reach the component. This implementation was discarded as, from user testing, it became apparent that many voiceover users do not use the rotor in their everyday life.
Custom Action
We considered making a specific gesture for skipping the carousel; for example, users could continue swiping right to navigate through the carousel or they can swipe up to skip to the next paragraph in the article. The custom gesture would be announced to the user when they reach the carousel. It is generally advised to not use custom actions, as voiceover users rarely want to learn new behaviour when using an app, so we also rejected this solution.
Headings
Voiceover users can skip between headings on a page, which is how apps such as BBC iPlayer get around this issue when displaying multiple carousels. Although this is a familiar means of navigation for a user, our carousels within articles do not necessarily have a heading above and beneath them, making this solution unfeasible.
Adjustable Action
Finally, we returned to our initial WWDC implementation and decided to append the word ‘button’ to the accessibility label for each carousel card. The full solution is documented below:
A custom UIAccessibilityElement class overrides the accessibility value and accessibility trait to make an ‘adjustable’ context that can be added to the carousel component:
override var accessibilityValue: String? {
get {
return currentItem?.value ?? super.accessibilityValue
} set {
super.accessibilityValue = newValue
}
}override var accessibilityTraits: UIAccessibilityTraits {
get {
return .adjustable
} set { }
}
A function was added to the view model that would return the string ‘button’ alongside a card at any index in the carousel:
public func accessibilityValue(for index: Int) -> String? {
if index < nestedViewModels.count, let nestedAccessibilityLabelString = nestedViewModels[index].accessibilityLabelString { return “\(nestedAccessibilityLabelString) \(String(localized: LocalizedString.accessibilityLabelButton, bundle: .main))” }
return nil
}
Conclusion
After carefully considering other options we opted for this solution, but will be iterating on the approach according to user feedback. Our overall impression is that this unique voiceover challenge is one Apple have not considered and a standardised carousel skipping behaviour would be ideal in this context.