Wesley de Groot's Blog
Accessibility in SwiftUI

Back

Accessibility in app development is all about making sure everyone, regardless of their abilities, can use your app. Apple’s SwiftUI framework makes integrating accessibility features more streamlined. Here, we'll explore key strategies to make your SwiftUI apps more inclusive.

Understanding Accessibility

Accessibility isn't just a checklist; it’s a mindset. Think of it as designing for the broadest range of human abilities and environments. It's about empathy, respecting your users, and enabling them to interact with your app seamlessly.

Accessibility in SwiftUI: Getting Started

SwiftUI simplifies the process with built-in accessibility modifiers.
Let’s cover some of the fundamental elements.

1. Labeling and Identifying Elements

The .accessibility(label:) modifier help users with assistive technologies understand your UI.

Text("Play")
    .accessibilityLabel(label: Text("Play button"))
    .accessibilityIdentifier("play_button")

.accessibilityIdentifier(_:) is useful for UI testing.

2. Providing Hints

Hints can help users understand the purpose of interactive elements.
The .accessibilityHint(_:) modifier provides additional information.

Button(action: {
    // Action
}) {
    Image(systemName: "trash")
}
.accessibilityHint(Text("Deletes the current item"))


3. Grouping Elements

Use the .accessibilityElement(children:) modifier to logically group UI elements together, improving navigation.

VStack {
    Text("Name")
    TextField("Enter your name", text: $name)
}
.accessibilityElement(children: .combine)


4. Hide non-essential Elements

Use the .accessibilityHidden(_:) modifier to hide non-essential elements from assistive technologies.

Text("I am here only for the fancy design!")
    .accessibilityHidden(true)


5. Accessibility speach language

Use the .accessibilityLanguage modifier to set the language of the accessibility label.

let attributedString = AttributedString(
    "Nederland is bekend om zijn tulpen en windmolens.",
    attributes: AttributeContainer([
        .accessibilitySpeechLanguage: "nl_NL"
    ])
)

Text(attributedString)


6. Accessibility Actions

You can create custom actions that VoiceOver users can trigger using the .accessibilityAction(named:_:) modifier.

Image(systemName: "heart")
    .accessibilityAction(named: Text("Like song")) {
        // Handle action
    }


7. Accesibility Announcements

Use AccessibilityNotification to provide custom announcements for VoiceOver users.

@State
private var isLoading = false

var body: some View {
    VStack {
        Button("Search catalog") {
            isLoading = true
        }

        if isLoading {
            ProgressView()
        }
    }
    // Track state changes
    .onChange(of: isLoading) { _, isLoading in
        if isLoading {
            UIAccessibility.post(
                notification: .announcement, 
                argument: "Search in progress"
            )
        }
    }
}


8. Accessibility focus state

Use the @AccessibilityFocusState and .accessibilityFocused(_:) modifier to manage the focus state of an element.

@AccessibilityFocusState
private var isEmailFocused: Bool

@State
private var email = ""

var body: some View {
    Form {
        TextField("Email", text: $email, prompt: Text("Email"))
            // Binds the focus state of this TextField to the 'isEmailFocused' property.
            .accessibilityFocused($isEmailFocused)
            // The border color changes based on whether the TextField is focused:
            .border(isEmailFocused ? .red : .clear)
    }
}

Sample taken from: https://appt.org/en/docs/swiftui/samples/accessibility-focus-indicator

9. Accessibility sort priority

Use the accessibilitySortPriority(_:) modifier to set the sort priority of an accessibility element.

VStack {
    Text("First Element")
        .accessibilitySortPriority(2) // Reads second
    Text("Second Element")
        .accessibilitySortPriority(3) // Reads first
    Text("Third Element")
        .accessibilitySortPriority(1) // Reads third
}
.accessibilityElement(children: .contain) // Groups the stack's children


10. Accessibility traits

Use the accessibilityAddTraits(_:) modifier to set the traits of an accessibility element.

// Button Trait
Text("Tap Me")
    .accessibilityAddTraits(.isButton)

// Header Trait
Text("Section Header")
    .font(.headline)
    .accessibilityAddTraits(.isHeader)

// Link Trait
Text("Visit Website")
    .foregroundColor(.blue)
    .underline()
    .accessibilityAddTraits(.isLink)

// Image Trait
Image(systemName: "star.fill")
    .accessibilityAddTraits(.isImage)

// Custom Checkbox Trait
@State
private var isSelected: Bool = false

var body: some View {
    CustomCheckbox(isSelected: $isSelected)
        .accessibilityAddTraits(isSelected ? [.isSelected] : [])
}


11. Accessibility value

Use the accessibilityValue(_:) modifier to set the value of an accessibility element.

@State
private var progress: Double = 0

var body: some View {
    MySlider(value: $progress)
        .accessibilityValue("\(progress)")
}


12. Dynamic Type

Support Dynamic Type by using the font modifier with the .largeTitle, .title, .headline, .body, or .caption text styles.

Text("Hello, World!")
    .font(.largeTitle)


14. Autocomplete

Autocompletion is an accessibility feature that helps users complete text input.
Use the .searchSuggestions(_:) modifier to provide search suggestions.

ProductList()
    .searchable(text: $text)
    // Provide search suggestions
    .searchSuggestions {
        Text("🍎").searchCompletion("apple")
        Text("🍐").searchCompletion("pear")
        Text("🍌").searchCompletion("banana")
    }


15. Dark Mode

Dark mode is an accessibility feature that helps users with light sensitivity or visual impairments.

@Environment(\.colorScheme)
var colorScheme

var body: some View {
    HStack {
        if colorScheme == .dark {
            // Show moon icon (dark mode)
            Image(systemName: "moon.fill")
        } else {
            // Show sun icon (light mode)
            Image(systemName: "sun.max.fill")
        }

        Text(colorScheme == .dark ? "In dark mode" : "In light mode")
    }   
}


16. Sensory Feedback

Sensory feedback, like haptic feedback, can help users with visual impairments navigate your app.
This can be achived using the UIImpactFeedbackGenerator and UINotificationFeedbackGenerator classes.

Button("Tap Me for Haptic Feedback") {
    UIImpactFeedbackGenerator(style: .heavy)
        .impactOccurred()

    // Action
}

Button("Tap Me for Success") {
    UINotificationFeedbackGenerator()
        .notificationOccurred(.success)

    // Action
}


Testing Accessibility

Regularly test your app's accessibility using tools like VoiceOver.
Navigate through your app and ensure that every element makes sense and that there are no barriers to interaction.

Conclusion

Accessibility is integral to creating inclusive, user-friendly apps. With SwiftUI's robust accessibility features, there's no excuse for not making your app usable by everyone. It's about designing with empathy and making sure your app stands out by being accessible to all.

Tip:

Use the Accessibility Inspector in Xcode to check your app's accessibility, and enable and disable accessibility features to see how your app behaves.

Resources:

Read more

Share


Share Mastodon Twitter LinkedIn Facebook
x-twitter mastodon github linkedin discord threads instagram whatsapp bluesky square-rss sitemap