Mastering App Development and UIAccessibility with Swift: Adventure application

0 of 13 lessons complete (0%)

Implementation of the different level types

Rotor Based Levels

A Rotor Based Level will rely on the UIAccessibilityCustomRotor concept, we will include spoken clues that will be displayed on multiple views on screen (the accessibility frame matches the view’s frame by default thus we will only define the frames), a rotor will be displayed on a validation view at the bottom of the screen, this is on this validation view that we will display the level answers:

We extract the generation of our “clues” into a dedicated RandomFramesGenerator component, this will allow us to reuse it for our levels that will need to display random view (e.g. Search Levels).

The Rotor can be used on any view element on iOS by the VoiceOver users, it, here we will attach some additional rotor options on the level exit: a locked door (we place the view at the bottom of the screen).

We configure reading hints when the user hovers our clues by tuning the accessibilityLabel property:

By default UIView is not an accessibility element, we need to specify it. Then we attach some game message our level locked door view:

We build the custom rotors and attach them to the locked door:

Generally, the custom rotor is used to go through large chunks of text by attaching a callback returning a UI element based on user slide direction (detected by inspecting the callback UIAccessibilityCustomRotorSearchPredicate predicate: predicate.searchDirection == UIAccessibilityCustomRotorDirection.next).

If we return a nil element, the VoiceOver rotor will focus no element, but we can add custom behaviour based on selected item.

class RotorLevel: Level { typealias Clue = String var clues: [Clue]
var hints: [String]

init(clues: [Clue], hints: [String] = [], answers: [Answer], validAnswers: [Answer]) { self.clues = clues
self.hints = hints
super.init(answers: answers, validAnswers: validAnswers)

} }

struct RandomFramesGenerator {
static func generateFrames(startY: Int = 165, count: Int) -> [CGRect] {

var frames: [CGRect] = [] for i in 0..<count {

let frame = CGRect(x: 80 * i, y: startY + 120 * i, width: 80, height: 80)

frames.append(frame) }

return frames }

}

private func setupClueViews() {
let frameCount = level.clues.count
let frames = RandomFramesGenerator.generateFrames(count: frameCount) for i in 0..<frameCount {

let clue = level.clues[i]

let frame = frames[i]

let clueView = UIView(frame: frame) clueView.isAccessibilityElement = true clueView.accessibilityLabel = clue self.clueViews.append(clueView) self.view.addSubview(clueView)

} }

private func setupLockedDoorView() {

let frame = CGRect(x: UIScreen.main.bounds.width/2 – 100/2, y: UIScreen.main.bounds.height – 100 – 60, width: 100, height: 100) self.lockedDoorView.frame = frame
self.lockedDoorView.isAccessibilityElement = true
self.lockedDoorView.accessibilityLabel = L10n.doorScript

// … }

let customRotors = makeAccessibilityCustomRotors(for: level.answers) self.lockedDoorView.accessibilityCustomRotors = customRotors self.view.addSubview(self.lockedDoorView)

It acts as some kind of filter, for example, if you have an app that shows a list of possible travel destinations, you can add a custom rotor option named “Filter Country” that will have countries list as its options and filter all selected countries rows.

In our example we need to validate the answer the user selects, we will provide one rotor option per answer, the callback will validate it, we do not need to move the focus on another UI element on selection thus we return nil in each callback:

That’s it! We used the UIAccessibilityRotor to add level validation options to our Rotor Based Levels. We will now focus on a similar core UIAccessibility concept, the UIAccessibilityCustomActions, in our Custom Actions Based Levels (Search Levels).

en_USEnglish