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 get in the wrong place. 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 for 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 UIScrollView and only then finds the top buttons. For the user, this behavior of VoiceOver will be wrong: the buttons are at the top, so the search and scoring should begin with them.

To get started, let’s first figure out how VoiceOver determines the order of controls. He does it like this: takes elements from a property accessibilityElements. By default, everyone is there view, which 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 way – from left to right, from top to bottom:

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

Point the first item to focus

Another reading order issue is when VoiceOver first opens the screen, it selects the upper 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 UINavigationController puts focus on the “Back” button, he 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 kind 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() {
    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 – for large changes when a new screen is displayed. It is because of it that when you open the screen, the focus rises to the first element of the screen.
  • .layoutChanged – for smaller screen changes. For example, you entered the card details, and after validation, the “Pay” button becomes available.
  • .announcement – to pronounce the text. Suitable for indicating errors or additional explanations. It works poorly after the user’s actions, because at that moment the button just pressed is spoken out. But you can postpone it a little in time, then everything will work.
    DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) {
    		UIAccessibility.post(notification: .announcement,
                             argument: text)
  • .pageScrolled – to pronounce the text after completing the scroll .UIScrollView speaks a message for you in the format “page 3 of 5”, but you can replace this text with your own. For example, say that you are in a new category of products.
  • .pauseAssistiveTechnology and .resumeAssistiveTechnology – To enable or disable VoiceOver speech.

Show modal windows natively

When working with VoiceOver can (and will) shoot all the jambs allowed in the development. For example, we made a feed of messages 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 ran into 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, not UIViewController with special UIPresentationController. VoiceOver refers to .firstResponder, and ours view they were not.

If there is no time for rewriting, then it is possible for view put property 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 frame of the pizza name, so the “i” button turned out to be the first focus element. Even though it is small, it is on the right and is generally not so important.

You can change the reading order through accessibilityElementsbut I will show a different way. VoiceOver uses property accessibilityFrame, just by default it matches the regular 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 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() {
        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 bigger with .inset, so it’s more convenient to press.

Using a frame and AccessibilityContainer You 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 USD4“, 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 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 the page loading, it makes a click every second, and when the page is loaded 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 channel Dodo Pizza Mobile.

Similar Posts

Leave a Reply