Advent of iOS Accessibility

Fri, Dec 6, 2024 5-minute read

Advent of iOS Accessibility

Starting something new: The Advent of iOS Accessibility. Twenty-four days of exploring some of the most common accessibility issues I’ve encountered, how to identify them, and—most importantly—how to fix them. Hope you enjoy the series!

Day 1

One of the accessibility issues I see more often in iOS apps, believe it or not, is unlabelled elements. This happens especially for buttons with an icon but no title. In those cases, you need to configure an accessibility label manually.

Calendar of the Advent of iOS Accessibility. Day 1. Buttons with no labels (especially buttons with an icon, but no title). Button with a bell, that intends to let the user see the notifications. If the button has no accessibility label, VoiceOver could say things like: button, bellIcon, possibly notification… far from ideal! A good accessibility label would be “Notifications”. In UIKit, you can just assign a localized string to the accessibilityLabel property for the button. In SwiftUI, you can use the accessibilityLabel modifier and pass in a Localized String Key.

Day 2

Some recommendations for improving your accessibility labels: don’t add the element type, avoid redundancy and verbosity, localize…

Calendar of the Advent of iOS Accessibility. Day 2. Better accessibility labels. A music player with some examples of how to improve your accessibility labels. Don’t add the element type: for the like button, if the label is “Like button”, VoiceOver will say “Like button, button”. So just “Like” is perfectly fine. Avoid redundancy and verbosity. For the repeat button, the label “Repeat This is the last time from Kean” would be too long, and if we specify the song and band for every action, it would be too verbose. Localize. For the next track button, try to use a localized string instead of a plain string so VoiceOver speaks in the correct language. Start with a capital letter and don’t end with a period. This helps with VoiceOver’s inflection.

Day 3

Anything representing a heading in the app should have the header trait. It allows for a faster way of exploring a screen and quickly jumping to the part of the app you are interested in. Screens should also start with a header.

Calendar of Advent of iOS Accessibility. Day 3. Missing header trait for headings. BBC News app shows two sections Technology and Science & Environment. These sections consist of a horizontally scrollable carrousel of at least 5 elements. If the titles of the sections have the header trait, a single swipe down brings you from one section to the other, compared to 6 swipes to the right needed otherwise. At the top of the app, there’s the BBC logo. Screens should start with a heading so the logo could also have the header trait. In UIKit you can insert the header accessibility trait to the accessibilityTraits property of the UI element representing the heading. In SwiftUI, you can use the accessibilityAddTraits modifier and pass isHeader as parameter.

Day 4

Important information is often conveyed visually through icons, badges, or progress bars… These details can easily be overlooked. Please make sure they’re part of your UI’s components' accessibility labels or values for a more inclusive experience.

Calendar of Advent of iOS Accessibility. Day 4. Visual cues missing from VoiceOver’s announcement. Four examples. The first one is a button with a person silhouette and a checkmark. VoiceOver announces it as “Collaborate, button”. It is missing the fact that you are already collaborating with someone. Represented by the checkmark. Second example shows a tab with the title “Coming soon” and a badge with the number 6. VoiceOver announces it as “Coming soon, Tab 2 of 5”. It is not conveying that there are 6 new movies and shows. Represented by the badge. Third example shows a chart’s title: “Daily Average 1h 55m”, an arrow pointing down, and “55% from last week”. VoiceOver says “Daily average, one hour fifty-five minutes. 55% from last week”. The user wouldn’t know if it went up or down. Represented by the arrow pointing down. Fourth example, episode download. The stop button is surrounded by circular progress bar. VoiceOver says “Stop downloads, button” but not how much is left.

Day 5

Images should either be decorative or have a proper accessibility label or alt text that describes them. If they’re decorative you can make it so they get skipped by assistive tech so it doesn’t get in the way of the experience.

Calendar of Advent of iOS Accessibility. Day 5. Images: with a description or decorative. iOS app with Disney+ showing the “How I met your mother” screen. The header has two images, one of them in the background, with the characters, that could be considered decorative. The other one is an image with the title of the series, which needs a description. In UIKit images are not accessible by default. So in the case of images that need to be accessible, you need to make the accessible elements. In SwiftUI images are by default accessible. You have an Image constructor with a decorative parameter, where you pass the name of the image.

Quick clarification on this one. It is probably not the best example of decorative images. I think these images should have alt text. But in my experience, most APIs won’t give you one for movie posters, music artwork, and things like that… And the point is that a random name just adds noise.

And another nuance. VoiceOver has a feature called Image Explorer. VoiceOver can recognise text, people and objects in images. So if you think that users can get value with this feature, you might want to consider exposing the image then.

Oh! And avoid images of text please!

Day 6

With regular buttons from UIKit or SwiftUI, you are all set. With complex views, headings, or table/collection view cells that, when selected, bring the user somewhere else in the app or perform an action, you’ll have to add the button accessibility trait yourself to convey users that it is an interactive component.

Calendar of Advent of iOS Accessibility. Day 6. Missing button accessibility trait. Four examples of not-so-obvious elements that require the button trait to be added by developers. First example, the collection view cells representing shortcuts in the Shortcuts app. Runs the shortcut when selected. Second example, the table view cell with a post in the Mastodon app. It opens the detail view and thread when selected. Third example, the What to Watch header in the Apple TV+ app. It can be selected and it will open the What to Watch screen. Fourth example, a view that is more complex than a regular button like a notification in Notification Centre that opens the notification.

Day 7

Grouping elements when it makes sense can make a huge impact on easing navigation with some assistive technologies like VoiceOver, Switch Control, or Full Keyboard Access. It also helps on reducing redundancy.

Calendar of Advent of iOS Accessibility. Day 7. Grouping elements for easier navigation. There is an example using the Foursquare app. There is a list of restaurants. Each restaurant has a name, type, location, and rate. By default, you’d need four swipes to the right with VoiceOver to go from one restaurant to the next one. That’s 32 swipes to go through 8 restaurants. If we group all these, it is just a single swipe to go from one item to the next one, easing navigation a lot. There is another example showing Next Door. The post in the example would require 9 swipes. So you can see how things can quickly get worse for more complex views. In that case, each post has a more options, like, reply, and share buttons that would repeat for every single item causing lots of redundancy.

In UIKit, this process consists of three steps:

  1. Setting the parent view as an accessible element: https://iosdev.space/@dadederk/109693895401281036
  2. Configure relevant accessibility label, value, traits and hints on the parent view: https://iosdev.space/@dadederk/109806337876803727
  3. If there are secondary actions within the grouped view, configure custom actions: https://iosdev.space/@dadederk/1097016

In SwiftUI, it might be as simple as using the .accessibilityElement(children:) modifier with the .combine accessibility child behaviour: https://iosdev.space/@dadederk/109932750048041110 If that doesn’t quite work as expected, try to tweak the internal views. If not, you can fall back to UIKit’s three step process.

Day 8

If a view has isAccessibilityElement to true, assistive tech won’t look for any of its subviews. That means that if there are any buttons inside, they won’t be accessible. You can add custom actions to be able to ‘interact’ with them.

Calendar of Advent of iOS Accessibility. Day 8. Inaccessible buttons within accessible views. Example of the Mastodon app. If we make the table view cell representing a post an accessible element so it is easier to navigate, the subviews, including the buttons, won’t be accessible anymore. So how can a VoiceOver, Keyboard, Voice Control, or Switch Control user interact with them? Custom actions to the rescue. In SwiftUI there is a new modifier, since iOS 16, called accessibilityActions. Before, you had to add the actions one by one. In UIKit, there is an accessibilityCustomActions property, which is an array of UIAccessibilityCustomAction.

Day 9

If you have interactions that are hidden or require complex gestures to be performed or that may conflict with VoiceOver, you need to provide alternative ways of executing these actions. Custom actions can help a lot of times, but not always.

Calendar of Advent of iOS Accessibility. Day 9. Hidden actions not being accessible. Find alternative ways of actioning hidden interactions or that require complex gestures or gestures that conflict with VoiceOver’s. Two examples. The first one is swiping left in a table view cell to unveil a delete option. That could be fixed by adding delete as a custom action. Second example is a bottom sheet menu with a grid of options. You need to swipe down to dismiss it. You’d probably need to implement the escape gesture for VoiceOver and it would be a good idea to add a close button anyway.

Day 10

Toggles or UISwitches are often found separated from the label that precedes (and describes) them; with an unclear label; missing a value, trait, or hint; or even not being actionable at all.

Calendar of Advent of iOS Accessibility. Day 10. Bad experience with toggles. Very often toggles or UISwitches are not grouped together with the label preceding them or lack of the right values, traits and hints. Three examples. With the first one VoiceOver just says “Shared accomodation” and it is not even actionable. The second one focuses first on the label and then on the switch. VoiceOver says “Event cancellations” and then “Switch button, on, doublet-tap to toggle setting”. The third one does a similar thing but treats the switch as a button. VoiceOver says: “Like” and then “Button”. All quite confusing. With UIKit you can configure your UISwitch as the accessoryView for a table view cell. With SwiftUI you can use a named Toggle that lets you specify an associated label.

Day 11

Have you ever seen VoiceOver randomly focusing on elements of the previous view when presenting a custom modal view? That can be fixed by letting the system know that the presented view is modal in terms of accessibility.

Calendar of Advent of iOS Accessibility. Day 11. Confusing custom modals. There is a groceries app. It has some text that says “Sorry, we don’t deliver there yet” a search field, a search button, a text that says “or” and a login button. When presenting a modal view on top of it, VoiceOver still focuses through the elements of the previous screen. To avoid that with UIKit, you can set accessibilityViewIsModal to true for the modal view. With SwiftUI there is an accessibility trait for that, .isModal.

Day 12

Sometimes we may fail to update users of things changing on the screen in a perceivable way. Toasts and similar should be announced. We may want to make clear that some content on the screen changed or to update on a task’s progress.

Calendar of Advent of iOS Accessibility. Day 12. Failing to update the user of changes. Three examples over the Apple TV app on the Ted Lasso page. The first one has a hud saying “Episode added to Up Next”. Toasts, snackbars and huds can be conveyed to VoiceOver usersa using accessibility announcements. The second one is a drop down to select the season which changes the episodes visible in the carrousel underneath it. A layout changed notification can help with changing content in the screen. The third one shows an episode with a download indicator. The updates frequently trait could help with that.

Day 13

Sometimes, you may want to create a custom component, even if there is a similar one in UIKit or SwiftUI because you want to style it in a way that the default one won’t let you. Or add extra functionality. That’s fine, but please take into account that you may need a bit of extra work to make it accessible.

Calendar of Advent of iOS Accessibility. Day 13. Custom components can need a bit of work. If you develop a custom tab bar, you need to set the tab bar accessibility trait in the container, then you need to set up the container type with the value semantic group, then give it a localised string, “Tab bar”, as an accessibility label. Also, you need to set up the large content viewer by setting show large content viewer to true and adding the interaction to the buttons… If you use Swift UI, the .accessibilityRepresentation(representation: ) modifier can help you create more accessible custom components.

Day 14

iOS and Xcode provide a wide variety of tools and options to deal with color, and help us providing good color contrast ratios. From system colors that automatically support Increase Contrast, to high contrast (and light and dark mode) color asset variants, automatic checks with the Audit feature in the Accessibility Inspector, and even a built-in contrast calculator.

Calendar of Advent of iOS Accessibility. Day 14. Color contrast. Some of the tools available in Xcode that help you create apps with great contrast are the asset catalog which lets you provide high contrast variants for color sets and assets, and the color contrast calculator. Some examples show how text size also affects contrast ratios. White on Black passes for all sizes. A grey with a 4.5 to 1 ratio over black will fail on small text sizes. A grey with a 2.9 to 1 ratio over black, fails for all text sizes. You can also use system colors an make sure that your app works well with the High Contrast setting.

Day 15

Touch target sizes are recommended to be at least 44 x 44 points for better usability. Buttons in the navigation bar (especially when not using nav bar button items), dismiss buttons, and custom toolbars, are common examples that often fall below this size.

Calendar of Advent of iOS Accessibility. Day 15. Small touch target sizes. Three examples of common cases with buttons that tend to have small touch target areas. The first one is buttons in the navigation bar. The second one is dismiss buttons for modal or inline popups. The third one is toolbars, especially custom ones.

Day 16

A reminder that the more modes we use to convey important information, the more likely it is that all users will perceive it. Consider a combination. of color, icons, messages, sound, haptics, animations, etc.

Calendar of Advent of iOS Accessibility. Day 16. Multimodal information. One example shows an app where you need to introduce a 6-digit pin. When it is the wrong pin, it does a shake animation on the pin field. It is using one mode: animation. If reduce motion is enabled, or for VoiceOver users, this information will not be perceived. The second example shows the same app but it adds a warning icon and a message that says “Incorrect pin” in red and the device also vibrates and does a sound to indicate the error. It uses several modes: animation, iconography, messaging, color, sound, and haptics. This is preferred.

Day 17

Check the traversal order of elements in your app. Sometimes the default top-left to bottom-right order might not be the most logical one. Sometimes you may consciously want to tweak the order. Other times, grouping is the answer.

Three examples of traversal order for three pieces of data in three columns. The first one says Followers and 550 underneath, the second one Following with 340 underneath, and the third one Posts with 750 underneath. In the first one, the order is: Followers, Following, Post, 550, 340, 750. This order is incoherent. For the second one, the order is: Followers, 550, Following, 340, Posts, 750. This one has a logical order. The third one’s order is: Followers 550, Following 340, Posts 750. Where both name and number for each one of the pieces of data is grouped. It is clearer and the navigation is easier.

If you want to find out more, we had an interesting conversation in Bluesky with Paul J. Adam, on the nuances of how to implement this.

Day 18

When building custom components, or if not relying on UIControl’s attributes to configure state, it can be easy to forget to specify the right accessibility traits. These are indispensable for a good experience with VoiceOver, Switch Control, Voice Control, Full Keyboard Access…

Calendar of advent of iOS Accessibility. Day 18. Missing traits. Three examples. The first one shows a custom segmented controller. These tend to have a missing selected trait for the selected option. The second one shows a custom page control. These should configure an adjustable trait. The third one shows a button that shows the disabled state by changing the color of the text instead of setting isEnabled to false. This means the button will be lacking the not enabled trait.

Day 19

This post is work in progress… One more sleep till the next tip! See you tomorrow!

You can also follow the series in: