Ein umfassender Leitfaden zum Erstellen barrierefreier iOS- und Android-Anwendungen - mit nativen Apps, Hybrid-Frameworks, Touch-Targets, Gesten, Screen Readern und plattformspezifischen Barrierefreiheitsfunktionen
Mobile Geräte sind zur primären Art geworden, wie Milliarden von Menschen auf digitale Inhalte und Dienste zugreifen. Für viele Nutzer mit Behinderungen sind mobile Geräte mit integrierten Barrierefreiheitsfunktionen wie Screen Readern, Sprachsteuerung und Vergrößerung erschwinglicher und zugänglicher als traditionelle assistive Technologien, die teure Desktop-Software oder -Hardware erfordern.
Wichtige Überlegungen zur mobilen Barrierefreiheit:
Beide großen mobilen Plattformen bieten robuste Barrierefreiheitsfunktionen, unterscheiden sich aber in der Implementierung:
| Funktion | iOS | Android |
|---|---|---|
| Screen Reader | VoiceOver (integriert) | TalkBack (integriert) |
| Aktivierung | Dreifach-Klick Home/Seitentaste oder Einstellungen | Lautstärketasten oder Einstellungen |
| Gesten | Nach rechts/links wischen zum Navigieren, Doppeltippen zum Aktivieren | Nach rechts/links wischen zum Navigieren, Doppeltippen zum Aktivieren |
| Sprachsteuerung | Voice Control (getrennt von Siri) | Voice Access |
| Vergrößerung | Zoom (3-Finger-Doppeltippen) | Vergrößerung (Dreifach-Tippen oder Taste) |
| Anzeigeoptionen | Display-Anpassungen, Größerer Text | Schriftgröße, Anzeigegröße, Farbkorrektur |
| Switch-Steuerung | Switch Control | Switch Access |
Mobile Apps müssen folgenden Standards entsprechen:
Alle UI-Elemente sollten für VoiceOver zugänglich sein. UIKit-Steuerelemente sind standardmäßig barrierefrei, aber benutzerdefinierte Views benötigen explizite Konfiguration.
// Swift - Making a custom view accessible
class CustomButton: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setupAccessibility()
}
func setupAccessibility() {
// Make the view accessible
isAccessibilityElement = true
// Set accessibility label (what the element is)
accessibilityLabel = "Submit form"
// Set accessibility trait (what kind of element)
accessibilityTraits = .button
// Optional: Set accessibility hint (what it does)
accessibilityHint = "Double tap to submit the form"
// Optional: Set accessibility value (current state)
// accessibilityValue = "Not submitted"
}
}
Best Practices für Labels und Hints:
// Good examples button.accessibilityLabel = "Play" button.accessibilityHint = "Starts playback" slider.accessibilityLabel = "Volume" slider.accessibilityValue = "\(Int(slider.value * 100))%" // Bad examples button.accessibilityLabel = "Play button" // "button" is redundant button.accessibilityHint = "Button" // Not helpful textField.accessibilityLabel = "Type here" // Should describe what to type
Traits sagen VoiceOver, welche Art von Element es ist und wie man damit interagiert.
// Common accessibility traits
.button // "Button" - double tap to activate
.link // "Link" - opens content
.searchField // "Search field" - brings up keyboard
.image // "Image" - non-interactive image
.staticText // Plain text
.header // Heading (navigable with rotor)
.selected // "Selected" - current state
.notEnabled // "Dimmed" - disabled state
.adjustable // Can be incremented/decremented with swipe up/down
// Combining traits
view.accessibilityTraits = [.button, .selected]
// Custom controls
view.accessibilityTraits = .adjustable
view.accessibilityValue = "Medium"
// Implementing adjustable
override func accessibilityIncrement() {
// User swiped up
increaseValue()
}
override func accessibilityDecrement() {
// User swiped down
decreaseValue()
}
Gruppieren Sie zusammenhängende Elemente, damit VoiceOver sie als ein einzelnes Element behandelt und so die Navigationskomplexität reduziert.
// Group a card with image, title, and description cardView.isAccessibilityElement = true cardView.accessibilityLabel = "\(title), \(description)" cardView.accessibilityTraits = .button // Hide child elements from VoiceOver imageView.isAccessibilityElement = false titleLabel.isAccessibilityElement = false descriptionLabel.isAccessibilityElement = false
SwiftUI bietet deklarative Modifikatoren für Barrierefreiheit:
// SwiftUI accessibility modifiers
Button(action: submit) {
Text("Submit")
}
.accessibilityLabel("Submit form")
.accessibilityHint("Double tap to submit")
.accessibilityAddTraits(.isButton)
// Grouping
HStack {
Image("star")
Text("4.5 stars")
Text("(120 reviews)")
}
.accessibilityElement(children: .combine)
.accessibilityLabel("Rating: 4.5 stars, 120 reviews")
// Custom actions
Text("Article title")
.accessibilityActions {
Button("Share") { shareArticle() }
Button("Bookmark") { bookmarkArticle() }
}
// Value and adjustable
Slider(value: $volume, in: 0...100)
.accessibilityLabel("Volume")
.accessibilityValue("\(Int(volume))%")
// Hidden elements
Image(decorative: "background")
.accessibilityHidden(true)
So testen Sie mit VoiceOver:
Content Descriptions sind das Android-Äquivalent zu Accessibility Labels.
<!-- XML -->
<ImageButton
android:id="@+id/playButton"
android:src="@drawable/ic_play"
android:contentDescription="@string/play_button" />
<!-- Decorative images -->
<ImageView
android:src="@drawable/decorative"
android:importantForAccessibility="no" />
// Kotlin - Setting programmatically
button.contentDescription = "Play audio"
// Dynamic content description
button.contentDescription = if (isPlaying) {
"Pause audio"
} else {
"Play audio"
}
<!-- Using android:hint -->
<EditText
android:id="@+id/emailInput"
android:hint="Email address"
android:inputType="textEmailAddress" />
<!-- Using android:labelFor for separate labels -->
<TextView
android:id="@+id/passwordLabel"
android:text="Password"
android:labelFor="@id/passwordInput" />
<EditText
android:id="@+id/passwordInput"
android:inputType="textPassword" />
// Kotlin - Adding custom actions
ViewCompat.addAccessibilityAction(
view,
"Share"
) { view, arguments ->
shareContent()
true
}
ViewCompat.addAccessibilityAction(
view,
"Bookmark"
) { view, arguments ->
bookmarkContent()
true
}
// User can access these via TalkBack's Actions menu
<!-- Group related content -->
<LinearLayout
android:screenReaderFocusable="true"
android:contentDescription="Product: Phone, $599, 4.5 stars">
<ImageView
android:src="@drawable/phone"
android:importantForAccessibility="no" />
<TextView
android:text="Phone"
android:importantForAccessibility="no" />
<TextView
android:text="$599"
android:importantForAccessibility="no" />
</LinearLayout>
<!-- Control focus order -->
<View
android:id="@+id/firstElement"
android:nextFocusForward="@+id/secondElement" />
// Compose accessibility modifiers
Button(
onClick = { submit() },
modifier = Modifier.semantics {
contentDescription = "Submit form"
role = Role.Button
}
) {
Text("Submit")
}
// Merge descendant semantics
Row(
modifier = Modifier.semantics(mergeDescendants = true) {
contentDescription = "4.5 stars, 120 reviews"
}
) {
Icon(Icons.Filled.Star, contentDescription = null)
Text("4.5")
Text("(120 reviews)")
}
// Custom actions
Text(
"Article title",
modifier = Modifier.semantics {
customActions = listOf(
CustomAccessibilityAction("Share") {
shareArticle()
true
},
CustomAccessibilityAction("Bookmark") {
bookmarkArticle()
true
}
)
}
)
// State descriptions
Checkbox(
checked = isChecked,
onCheckedChange = { isChecked = it },
modifier = Modifier.semantics {
stateDescription = if (isChecked) "Checked" else "Unchecked"
}
)
So testen Sie mit TalkBack:
WCAG 2.1 Erfolgskriterium 2.5.5 (Level AAA) erfordert Touch-Targets von mindestens 44×44 CSS-Pixeln. Sowohl iOS als auch Android empfehlen mindestens 44×44 Punkte (iOS) / 48×48 dp (Android).
/* iOS - Minimum touch target */
button.frame(minWidth: 44, minHeight: 44)
// Android - Minimum touch target
<Button
android:minWidth="48dp"
android:minHeight="48dp" />
// If visual size must be smaller, increase touch area
view.frame(width: 24, height: 24)
.contentShape(Rectangle())
.padding(10) // Creates 44×44 touch area
Touch-Target Best Practices:
WCAG 2.1 erfordert Alternativen zu pfadbasierten und mehrpunktigen Gesten (2.5.1).
// GOOD: Pinch to zoom with buttons
ZoomableImage()
.zoomButtons() // Provides +/- buttons as alternative
// GOOD: Swipe to delete with button
List {
ForEach(items) { item in
ItemRow(item)
.swipeActions {
Button(role: .destructive) {
delete(item)
} label: {
Label("Delete", systemImage: "trash")
}
}
}
}
// BAD: Required complex gesture with no alternative
// Drawing signature with no "Type name" option
Bieten Sie Alternativen zu bewegungsbasierten Steuerungen (Schütteln, Neigen) gemäß WCAG 2.5.4.
// GOOD: Shake to undo with button alternative
.onShake {
undo()
}
// Also provide:
Button("Undo", action: undo)
// GOOD: Respect motion preferences
if !accessibilityPrefersReducedMotion {
// Use motion-based feature
} else {
// Use static alternative
}
Unterstützen Sie Benutzer-Textgrößen-Einstellungen (WCAG 1.4.4 - Text vergrößern, 1.4.10 - Umbruch).
// iOS - Dynamic Type
Text("Hello")
.font(.body) // Automatically scales with user settings
// Custom fonts with scaling
.font(.custom("MyFont", size: 17, relativeTo: .body))
// Android - Scalable text
<TextView
android:text="Hello"
android:textSize="16sp" /> <!-- Use sp, not dp -->
// Compose
Text(
"Hello",
fontSize = 16.sp // Scales with user settings
)
Text-Skalierungs Best Practices:
Unterstützen Sie sowohl Hoch- als auch Querformat, es sei denn, eine bestimmte Orientierung ist wesentlich (WCAG 1.3.4).
Mobile Geräte werden oft im Freien bei hellem Sonnenlicht verwendet, was Kontrast noch kritischer macht.
// iOS - Dark mode support
Color("PrimaryText") // Defined in asset catalog with light/dark variants
Text("Important")
.foregroundColor(.primary) // Automatically adapts
// Android - Dark mode
<resources>
<color name="primaryText">#000000</color>
</resources>
<!-- res/values-night/colors.xml -->
<resources>
<color name="primaryText">#FFFFFF</color>
</resources>
import { View, Text, TouchableOpacity } from 'react-native';
<TouchableOpacity
accessible={true}
accessibilityLabel="Submit form"
accessibilityHint="Double tap to submit"
accessibilityRole="button"
onPress={handleSubmit}>
<Text>Submit</Text>
</TouchableOpacity>
// Hide decorative elements
<Image
source={require('./decoration.png')}
accessibilityElementsHidden={true} // iOS
importantForAccessibility="no" // Android
/>
// Group elements
<View
accessible={true}
accessibilityLabel="Product: Phone, $599, 4.5 stars">
<Image source={productImage} />
<Text>Phone</Text>
<Text>$599</Text>
<Text>4.5 ★</Text>
</View>
// Flutter accessibility
Semantics(
label: 'Submit form',
hint: 'Double tap to submit',
button: true,
child: ElevatedButton(
onPressed: submit,
child: Text('Submit'),
),
)
// Merge semantics
Semantics(
label: '4.5 stars, 120 reviews',
child: Row(
children: [
Icon(Icons.star),
Text('4.5'),
Text('(120 reviews)'),
],
),
)
// Exclude from semantics
ExcludeSemantics(
child: Image.asset('decoration.png'),
)