iOSinput-navigationIntermediate

Customising Gestures for an Accessible Experience

Custom gestures can sometimes conflict with VoiceOver's own swipe commands, making your app challenging or impossible to use for some. This guide shows how to implement and customise gestures for an Accessible experience

🎯 Why It Matters

Many iOS apps use gestures to streamline interactions—think of swiping to delete an email or pinching to zoom in a photo gallery. However, users who rely on assistive technologies like VoiceOver interact with these gestures in a different way. Custom gestures can sometimes conflict with VoiceOver's own swipe commands, making your app challenging or impossible to use for some.

Real-World Context: Swipe to Delete or Archive

A common feature in email clients and messaging apps is the "swipe to delete" or "swipe to archive" action. While this gesture feels natural to many users, it can be problematic for people using screen readers if there is no accessible fallback. Without an alternative, VoiceOver users might not be able to activate this action at all, because VoiceOver already uses swipe gestures for navigation.

Key accessibility barriers this solution addresses:

  • Gesture Conflicts: Custom gestures that interfere with VoiceOver's navigation commands (swipe right/left to move between elements)
  • Discoverability: Gesture-only actions that aren't announced or discoverable by screen readers
  • Alternative Access: Users with limited dexterity or motor impairments who cannot perform complex multi-touch gestures

đź’» Implementation

Incorrect Code Example: Custom Swipe That Conflicts with VoiceOver

Demo Description

This code simulates a list where swiping on an item archives it, but it doesn’t offer a workable method for assistive technology users.

// SwiftUI
// INACCESSIBLE: Custom swipe gesture isn't announced or discoverable by VoiceOver.
 
import SwiftUI
 
struct EmailListView: View {
    @State private var emails = ["Meeting notes", "Shopping list", "Vacation plans"]
 
    var body: some View {
        List {
            ForEach(emails, id: \.self) { email in
                Text(email)
                    .gesture(
                        DragGesture(minimumDistance: 50)
                            .onEnded { value in
                                if value.translation.width < 0 {
                                    emails.removeAll { $0 == email }
                                }
                            }
                    )
            }
        }
    }
}

Code Explanation

Here, a left swipe removes an email from the list. However, VoiceOver users rely on swipe gestures to navigate through the list itself, so this custom gesture may interfere with or never trigger under VoiceOver. There’s no button or menu option to achieve the same action, leaving some users stuck.

Correct Code Example: Providing a Visible or Accessible Button

Demo Description

This snippet modifies the same feature to include a button that VoiceOver can discover, ensuring everyone can archive emails.

// SwiftUI
// ACCESSIBLE: VoiceOver users can tap the visible Archive button.
  
import SwiftUI
  
struct AccessibleEmailListView: View {
    @State private var emails = ["Meeting notes", "Shopping list", "Vacation plans"]
  
    var body: some View {
        List(emails, id: \.self) { email in
            HStack {
                Text(email)
                Spacer()
                Button {
                    emails.removeAll { $0 == email }
                } label: {
                    Image(systemName: "archivebox")
                        .accessibilityLabel("Archive \(email)")
                }
            }
            .gesture(DragGesture().onEnded {
                if $0.translation.width < 0 {
                    emails.removeAll { $0 == email }
                }
            })
        }
    }
}

Code Explanation

In this corrected code, the user can still swipe left to archive emails if they’re not using VoiceOver. However, we also include a visible button that VoiceOver can read with a clear accessibility-Label function, parsing the value of “Archive” followed by the email. This ensures people who rely on screen readers have a straightforward way to archive emails without performing a conflicting gesture.

Correct Code Example: Offering an Accessible Alternative to Pinch-to-Zoom

Demo Description

Let’s look at another example which shows how to keep the familiar pinch-to-zoom interaction while adding “Zoom out” and “Zoom in” buttons that VoiceOver can discover, giving people with limited dexterity or screen-reader users a one-finger alternative.

// SwiftUI  
 
import SwiftUI

struct PhotoGalleryView: View {
    @State private var scale = 1.0
    var imageName: String
    
    var body: some View {
        Image(imageName)
            .resizable()
            .scaledToFit()
            .scaleEffect(scale)
            .gesture(MagnificationGesture().onChanged { scale = $0 })
            .overlay(
                HStack {
                    Button({ scale -= 0.1 }) { 
                        Image(systemName: "minus.magnifyingglass")
                            .accessibilityLabel("Zoom Out")
                    }
                    Button({ scale += 0.1 }) { 
                        Image(systemName: "plus.magnifyingglass")
                            .accessibilityLabel("Zoom In")
                    }
                }
            )
    }
}

Code Explanation

Here, users can pinch to zoom by default. However, if they find the pinch gesture difficult—or if VoiceOver conflicts with multi-touch gestures—they can tap the “Zoom In” or “Zoom Out” buttons, which are labelled for accessibility.

âś… Best Practices

Do's

  • âś“Offer an alternative method for every custom gesture (buttons, menus, or accessibility actions)
  • âś“Preserve standard iOS gestures where possible—avoid reinventing them
  • âś“Provide clear accessibility labels for all alternative controls
  • âś“Use `.accessibilityAction()` for custom actions with user-friendly names
  • âś“Test all gestures with VoiceOver enabled to verify they work or have accessible alternatives
  • âś“Make alternative controls visible when possible, not just available to assistive technologies
  • âś“Ensure multi-touch gestures have single-touch alternatives

Don'ts

  • âś—Create gesture-only interactions without accessible alternatives
  • âś—Override standard iOS gestures that conflict with VoiceOver navigation
  • âś—Hide critical functionality behind complex multi-touch gestures
  • âś—Assume all users can perform pinch, rotate, or multi-finger gestures
  • âś—Forget to label icon-only buttons used as gesture alternatives
  • âś—Make gestures the only way to discover or access features
  • âś—Implement custom gestures that interfere with system-level accessibility features

🔍 Common Pitfalls

Gesture-Only Actions

Implementing swipe-to-delete, pinch-to-zoom, or other gestures without any alternative method for triggering the same action

Conflicting Gestures

Creating custom swipe gestures that interfere with VoiceOver's navigation (swipe right to go forward, swipe left to go back)

Undiscoverable Actions

Custom gestures that aren't announced by VoiceOver or documented anywhere in the interface

Missing Accessibility Actions

Failing to use `.accessibilityAction()` to expose gesture-based functionality to assistive technologies

Icon-Only Alternatives

Providing alternative buttons but forgetting to add accessibility labels to icon-based controls

Complex Gesture Requirements

Requiring precise or complex gestures (multi-finger swipes, long presses with drag) without simpler alternatives

VoiceOver/TalkBack

Screen readers use swipe gestures for their own navigation (swipe right to move forward, swipe left to move back, swipe up/down to adjust controls). Custom swipe gestures will conflict with these commands. When VoiceOver is enabled, provide button alternatives or use `.accessibilityAction()` to make custom actions available through the VoiceOver rotor. VoiceOver users can access custom actions by selecting an element and swiping up or down to choose from available actions

Switch Control

Users relying on switch control cannot perform multi-touch or complex gestures. All functionality must be accessible through single-tap alternatives or sequential single-switch inputs

Voice Control

Gesture-based interactions aren't accessible via voice commands. Alternative buttons with clear labels enable voice control users to say "Tap Archive" or "Tap Zoom In"

AssistiveTouch

Users with motor impairments who use AssistiveTouch may struggle with precise swipes or multi-touch gestures. Visible button alternatives make all functionality accessible

Full Keyboard Access

Gesture-based interactions are not accessible to keyboard users. Providing button alternatives ensures keyboard navigation works properly

đź§Ş Testing Steps

  1. 1Enable VoiceOver: Settings → Accessibility → VoiceOver
  2. 2Navigate to gesture-based controls: Swipe through your interface
  3. 3Test standard navigation: Verify VoiceOver swipe gestures work correctly
  4. 4Attempt custom gestures: Try to trigger custom gestures with VoiceOver enabled
  5. 5Locate alternative controls: Check that alternative buttons or actions are discoverable
  6. 6Test multi-touch gestures: Verify pinch-to-zoom and other multi-touch interactions
  7. 7Use alternative methods: Test zoom buttons, archive buttons, and other alternatives
  8. 8Enable Switch Control: Test that all functionality is accessible via single-switch input
  9. 9Test with Voice Control: Verify alternative controls can be activated by voice

Become a member to read this solution, and all of Ma11y.

This resource is part of our member knowledge base. Log in or create an account to unlock:

  • Complete accessibility guidance and implementation examples
  • Curated compliance frameworks and checklists
  • Early access to new tools and features