These are work-in-progress notes reflecting our ongoing efforts to improve the user experience for the users with visual impairment on the Pocket FM app. Please feel free to reach out if you notice any issues or discrepancies.
While building iOS apps with the best intentions, accessibility is usually taken as an add-on vs an integral part of the experience. Especially when it comes to blind users who rely on VoiceOver. Despite Apple’s clear accessibility framework guidelines, many apps unknowingly add barriers that make navigation, understanding and interaction difficult.
In this article, we explore the most common issues blind users face when using VoiceOver, why these issues matter and how we can fix them using best practices in SwiftUI. It is important to note that the discussion is based on Swift UI.
1. Missing or Inadequate Labels
One of the most common accessibility issues I have observed is the lack of proper labels on interface elements, especially icons and images. In cases where a button is represented only by an image or SF Symbol without a meaningful label, VoiceOver might read out an unhelpful string like “star fill,” “image,” or simply “button”.
Fix: Use .accessibilityLabel
to describe what the element is or does.
Image(systemName: "star.fill")
.accessibilityLabel("Favorite")
Every interactive control or image that has a meaning should be labelled with clear, concise language so that easy to navigate the interface.
2. Incorrect or Absent Accessibility Traits
Accessibility traits are there to inform VoiceOver about how an element should behave, whether it is a button, header, link or something else. If these are missing or incorrect, VoiceOver users may not realise an element is interactive/ how to interact with it.
Example: A tappable Text
view without a .isButton
trait will not be announced as a button. Similarly, an image used as a button will just be spoken as “image” unless the image trait is removed and replaced with the appropriate interactive trait.
Fix: Use .accessibilityAddTraits()
to define roles explicitly.
Text("Continue")
.onTapGesture { /* action */ }
.accessibilityAddTraits(.isButton)
Use .accessibilityRemoveTraits(.isImage)
to remove unnecessary traits for images acting as buttons.
3. Poor Grouping of Related Content
In cases where related elements are not grouped, such as a title, subtitle, and icon within a card, VoiceOver treats them as separate elements. This results in a fragmented reading experience, requiring extra swipes and cognitive load.
Fix: Use .accessibilityElement(children: .combine)
or .accessibilityElement(children: .ignore)
and assign a unified label.
VStack {
Text("John Doe")
Text("Software Engineer")
}
.accessibilityElement(children: .combine)
It reduces swipe count and keeps related information together.
4. Lack of Hints or Action Feedback
When the functionality or feedback is not clear from the label alone, users may hesitate to activate it. Similarly, if tapping a control changes something elsewhere in the UI, but VoiceOver does not announce the update, users will not be aware of those changes.
Fix:
- Add
.accessibilityHint()
to explain what will happen. - Use
UIAccessibility.post
to announce important changes.
Button(action: { addToCart() }) {
Image(systemName: "cart.badge.plus")
}
.accessibilityLabel("Add to cart")
.accessibilityHint("Adds this item to your shopping cart")
UIAccessibility.post(notification: .announcement, argument: "Item added to cart")
Clear hints and timely feedback help the VoiceOver users to stay informed and confident in their interactions.
5. Inaccessible Custom Controls and Gestures
As designers, we love to create custom-designed components that bring that awe flare in the designs. Like sliders, charts, drawing canvases or gesture-based interfaces, often look beautiful but are frequently inaccessible. Without proper labelling or accessible interaction paths, these custom views are invisible or non-functional for blind users.
Fix: Wrap custom views with accessibility descriptions and provide alternative gesture methods through .accessibilityAction()
or .accessibilityAdjustableAction
.
ZStack {
// Custom dial UI
}
.accessibilityElement(children: .ignore)
.accessibilityLabel("Volume")
.accessibilityValue("75 percent")
.accessibilityAdjustableAction { direction in
// Increase or decrease volume
}
Additionally, use .accessibilityAction(named:)
to expose actions that cannot be performed through standard gestures.
Apple’s Accessibility Guidelines
Apple’s Human Interface Guidelines offer a foundation for building inclusive interfaces. For VoiceOver support, key principles include:
Provide Descriptive Labels
Every UI control must have a spoken label. SwiftUI’s .accessibilityLabel
modifier ensures elements are properly identified.
Button(action: {}) {
Image(systemName: "gear")
}
.accessibilityLabel("Settings")
Include Hints for Complex Actions
For actions with not so obvious outcome, use .accessibilityHint()
to explain the results or how to interact.
.accessibilityHint("Deletes this conversation permanently")
Use Appropriate Traits and Roles
Add .accessibilityAddTraits()
to identify an element as a button, link, or header. Use .accessibilityRemoveTraits()
to remove misleading traits.
Text("Account Settings")
.accessibilityAddTraits(.isHeader)
This enables navigation via VoiceOver’s rotor (e.g., “Headings” mode).
Ensure Logical Reading Order
Arrange views top to bottom and left to right. Use .accessibilitySortPriority()
to adjust order if needed, but rely primarily on natural layout.
Group Related Information
Combine related content using .accessibilityElement(children: .combine)
for smoother navigation.
HStack {
Image(systemName: "location.fill")
Text("Bangalore, India")
}
.accessibilityElement(children: .combine)
Support Dynamic Content Announcements
Use UIAccessibility.post(notification: .announcement, ...)
to alert VoiceOver users of important changes in the UI.
UIAccessibility.post(notification: .screenChanged, argument: newView)
Also test accessibility with Dynamic Type, Reduce Motion, and other system preferences turned on.
Conclusion
VoiceOver accessibility is not optional for an inclusive user experience. Many of the challenges blind users face, missing labels, improper traits, fragmented navigation, and inaccessible custom components, are solvable with SwiftUI’s native accessibility tools.
Accessibility is not a feature. It is a foundation.
Sources
- Apple Human Interface Guidelines: Accessibility
- Deque Systems: Mobile Accessibility Pitfalls and Best Practices
- Apple Developer Documentation: UIAccessibility API
- WWDC Sessions: What’s new in accessibility”, Apple WWDC 2022–2024