VoiceOver on iOS: Solving Common Problems

When you try to adapt the application for the blind, often something goes wrong: either the order will be lost, then the focus will not get there. On the other hand, there is UX, which is easy to miss because you don’t know about possible problems. In this article we will deal with typical problems and their solutions.



Adaptation of the iOS application is a big topic, everything did not fit into one article, so I release them in a series.

  1. Voice Control and VoiceOver: how to adapt the application for the blind or still .
  2. VoiceOver on iOS: Each control behaves differently.
  3. VoiceOver on iOS: Solving Common Problems.
  4. The difference between the implementation of VoiceOver, Voice Control and UI tests. (In progress)

In the first part, we dealt with the adaptation of applications for the blind using VoiceOver: signed controls, grouped them, fixed navigation. In the second part, we went further and examined the “features” that can be given to controls in order to improve their work for blind people and generally improve the usability of the application.

Today we will continue to work on adapting the pizza screen: we will change the crawl order, summarize the purchase information, fix the modal window and improve the loading indicator.

Controls reorder


If you do not set the correct order of passing controls for VoiceOver, most likely it will bypass and read out the elements in the wrong order as you would like. For example, this happened with the buttons “close” and “to the basket”.

The screen will scroll and the buttons are above UIScrollView. It turns out that VoiceOver first tries to bypass all the elements inside UIScrollViewand only then finds the top buttons. For the user, this behavior of VoiceOver will be wrong: the buttons are at the top, so iterating and scoring should begin with them.

To get started, let's first figure out how VoiceOver determines the order of controls. He does it this way: takes elements from a property accessibilityElements. By default, all viewof them are there isAccessibilityElement = true.

Now we can put the buttons at the beginning by overriding accessibilityElements:

override var accessibilityElements: [Any]? {
    get {
        var elements = [Any]()
            
        elements.append(contentsOf: [closeButton, cartButton])
        elements.append(contentsOf: contentScrollView.accessibilityElements)
            
        return elements
    }
    set { }
}

Group through shouldGroupAccessibilityChildren


Typically, VoiceOver tries to read elements in a natural order - from left to right, from top to bottom:



If you have grouped controls, then you need VoiceOver to jump to the nearest element in the grouping, and not in reading order. Set .shouldGroupAccessibilityChildren = true, and the order will begin to take into account the proximity of the elements. The property must be set as the parent viewfor all elements.



Point the first item to focus


Another reading order issue is when VoiceOver first opens the screen, it selects the top left element. Most often, this is the back button. On the one hand, this allows you to quickly return to the previous screen if you make a mistake. On the other hand, this is how we lose our understanding of what screen we are on. You can correct the situation if you manually set the focus to the desired control.

What does Apple do?
It is strange that UINavigationControllerputs the focus on the "Back" button, I could put it on the title of the screen that opens. From the convenience side, it seems to me right to focus on the title or first control, so we give more information about the new screen. You can go back with a scrub gesture .

You can rearrange the focus using the alert function UIAccessibility.post(notification: …). It takes two parameters:

  • One of the species UIAccessibility.Notification.
  • The object to which the alert should be applied. Most often, this is a line with text or an object that must be selected after the notification.

You can put focus on the header in viewDidLoad:

override func viewDidLoad() {
    super.viewDidLoad()
        
    UIAccessibility.post(notification: .screenChanged,
                         argument: titleLabel);
}

You can transfer the object on which you want to put focus or the text to be pronounced.

Types of alerts for objects


  • .screenChanged — , . - .
  • .layoutChanged — . , , «» .
  • .announcement — . . , . , .

    DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) {
    		UIAccessibility.post(notification: .announcement,
                             argument: text)
    }
  • .pageScrolled.UIScrollView « 3 5», . , , .
  • .pauseAssistiveTechnology .resumeAssistiveTechnology —  VoiceOver.

Show modal windows natively


When working with VoiceOver can (and will) shoot all the jambs allowed in the development. For example, we made a message feed and so that the messages started from the bottom, we decided to flip UITableViewand then flip all the cells. Visually, everything is fine, but the list will scroll upside down with three fingers in VoiceOver.

We also faced a problem that we could not change the ingredients in any way, because it was not possible to put focus on the window. It happened because we showed the view, and not UIViewControllerwith a special UIPresentationController.VoiceOver it addresses .firstResponder, and ours viewwas not.



If there is no time for rewriting, then you can viewset the property for accessibilityViewIsModal. Then VoiceOver will focus only on that view.

override var accessibilityViewIsModal: Bool {
    get { return true }
    set {}
}

To be honest, it never worked for me, and we UIPresentationController.

Align Invisible Frames


Reading order is counted by frames, sometimes it gives unexpected results. For example, we enlarged the frame of the “i” button to make it easier to click. But it turned out to be higher than the pizza name frame, so the first element of focus was the “i” button. Even though it is small, it is on the right and is generally not so important.



You can change the reading order through accessibilityElements, but I will show a different way. VoiceOver uses the property accessibilityFrame, just by default it matches the usual frame. There are several solutions:

  1. Override control subclass and return a reduced value.
  2. Set the correct frame outside.
  3. Just adjust the frame so that it is flush with the inscription.

But it is important that this frame is in the coordinates of the screen. For simple conversion there is a function UIAccessibility.convertToScreenCoordinates.

It can also be used to combine controls. For example, you need to combine the switcher and its signature, so the element will become larger, it will be easier to click on it, unnecessary duplication will go away.

override func layoutSubviews() {
        super.layoutSubviews()
        repeatSwitch.accessibilityLabel = repeatLabel.text
        repeatSwitch.accessibilityFrame = UIAccessibility.convertToScreenCoordinates(
                repeatSwitch.frame.union(repeatLabel.frame).insetBy(dx: -12, dy: -12),
                in: repeatSwitch.superview!)
}

I also made the focus larger using .inset, it’s more convenient to press.



Using the frame and AccessibilityContaineryou can make graphs and tables available.

Summarize the main action


This is more about UX, but I’ll tell you anyway. A person with normal vision easily reads all the settings from the screen, but for this blind person, you need to manually sort through all the controls. You can facilitate the process and summarize all the changes in the Buy button.

For example, to get “Buy button. Add bacon, remove jalapenos. Price 434₽ », you don’t need to write anything unusual in the code, just collect the line with added / removed:

accessibilityTraits = .button
accessibilityLabel = ""
accessibilityValue = " ,  .  434₽" 

And don't forget to sign the download indicator


If after adding a product to the cart you briefly block the interface and download something, do not forget to make the download indicator available:

  1. Use the alert to focus on the indicator.
  2. Give the focus a name. For example, loading.
  3. Put it accessibilityViewIsModal.
  4. Let me know when the download is finished.

If after loading you show a new screen, then VoiceOver will itself switch focus, so it will become clear that the download has ended. In more complicated cases, you can explicitly say this or even add vibration .

What does Apple do?
Interestingly, Safari works with this in iOS 13: during a page load, it makes a click every second, and when a page loads it does * woop-dumb *. Alas, from the api side this is not yet available, we are waiting for iOS 14.



To adapt the screen, we used different approaches: we changed the order of the buttons, indicated the first element for focus, fixed the modal window, summarized the settings on the main button and worked on the convenience of the application. This knowledge is enough to adapt almost any application. Go ahead.

Next time I’ll talk about the difference between the implementation of VoiceOver, Voice Control and UI tests.
In order not to miss the next article, subscribe to my Dodo Pizza Mobile channel .

All Articles