Accessibility ensures your app is usable by everyone. Learn to implement accessibility features in iOS apps.
Accessibility Labels
Basic Labels
swift
import SwiftUI
struct AccessibilityView: View {
var body: some View {
VStack {
// Good accessibility
Image(systemName: "star.fill")
.accessibilityLabel("Favorite")
.accessibilityHint("Mark as favorite")
// Bad accessibility
Image(systemName: "star.fill")
}
}
}Custom Accessibility Labels
swift
struct CustomAccessibilityView: View {
@State private var isOn = false
var body: some View {
Toggle("Dark Mode", isOn: $isOn)
.accessibilityLabel("Enable dark mode")
.accessibilityHint("Changes app appearance to dark theme")
}
}VoiceOver Support
Screen Reader Support
swift
struct VoiceOverView: View {
@State private var selectedRating = 3
var body: some View {
VStack {
// Custom accessibility for rating
HStack {
ForEach(1...5, id: \.self) { rating in
Image(systemName: rating <= selectedRating ? "star.fill" : "star")
.accessibilityLabel(rating <= selectedRating ? "Selected" : "Unselected")
.accessibilityAddTraits(rating <= selectedRating ? [.isSelected] : [])
.onTapGesture {
selectedRating = rating
}
}
}
.accessibilityLabel("Rating")
.accessibilityValue("\(selectedRating) out of 5")
}
}
}Accessibility Actions
swift
struct AccessibilityActionsView: View {
@State private var isFavorite = false
var body: some View {
Image(systemName: "heart")
.foregroundColor(isFavorite ? .red : .gray)
.accessibilityLabel(isFavorite ? "Remove from favorites" : "Add to favorites")
.accessibilityAction(.default) {
isFavorite.toggle()
}
.accessibilityAction(.magicTap) {
// Double-tap with two fingers
isFavorite.toggle()
}
}
}Dynamic Type
Support Text Scaling
swift
struct DynamicTypeView: View {
var body: some View {
VStack(alignment: .leading, spacing: 16) {
Text("Headline")
.font(.headline)
.dynamicTypeSize(...DynamicTypeSize.accessibility3)
Text("Body")
.font(.body)
.dynamicTypeSize(...DynamicTypeSize.accessibility3)
Text("Caption")
.font(.caption)
.dynamicTypeSize(...DynamicTypeSize.accessibility3)
}
.padding()
}
}Scalable Layouts
swift
struct ScalableView: View {
@Environment(\.dynamicTypeSize) var dynamicTypeSize
var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: dynamicTypeSize == .accessibility1 ? 24 : 16) {
Text("Dynamic Type Support")
.font(.title)
.fontWeight(.bold)
Text("This text scales with your device settings")
.font(.body)
}
.padding()
}
}
}Accessibility Elements
Accessible Images
swift
struct AccessibleImageView: View {
var body: some View {
// Decorative image
Image(systemName: "star.fill")
.accessibilityHidden(true)
// Meaningful image
Image(systemName: "person.crop.circle.fill")
.accessibilityLabel("Profile picture")
// Image with description
Image(systemName: "photo")
.accessibilityLabel("Photo of a sunset")
.accessibilityHint("Tap to view full size")
}
}Accessible Groups
swift
struct AccessibilityGroupsView: View {
let user = User(name: "John Doe", email: "john@example.com")
var body: some View {
HStack {
Circle()
.fill(Color.blue)
.frame(width: 50, height: 50)
VStack(alignment: .leading) {
Text(user.name)
.font(.headline)
Text(user.email)
.font(.caption)
.foregroundColor(.secondary)
}
}
.accessibilityElement(children: .combine)
.accessibilityLabel("User: \(user.name), \(user.email)")
}
}
struct User {
let name: String
let email: String
}Semantic UI
Semantic Views
swift
struct SemanticView: View {
var body: some View {
VStack {
// Header
Text("Welcome")
.font(.title)
.accessibilityAddTraits(.isHeader)
// Button
Button("Get Started") {
// Action
}
.accessibilityHint("Starts the onboarding process")
// Link
Text("Learn more")
.accessibilityAddTraits(.isLink)
.onTapGesture {
// Navigation
}
}
}
}Accessibility Traits
Common Traits
swift
struct AccessibilityTraitsView: View {
@State private var isSelected = false
var body: some View {
VStack {
// Button trait
Button("Tap me") {}
.accessibilityAddTraits(.isButton)
// Selected state
Image(systemName: isSelected ? "checkmark.circle.fill" : "circle")
.accessibilityAddTraits(isSelected ? [.isSelected] : [])
// Static text
Text("Information")
.accessibilityAddTraits(.isStaticText)
// Header
Text("Section Title")
.accessibilityAddTraits(.isHeader)
}
}
}Accessibility Identifier
UI Testing Support
swift
struct TestableView: View {
var body: some View {
VStack {
TextField("Username", text: $username)
.accessibilityIdentifier("usernameField")
SecureField("Password", text: $password)
.accessibilityIdentifier("passwordField")
Button("Login") {
// Login action
}
.accessibilityIdentifier("loginButton")
}
}
}Reduced Motion
Support Motion Preferences
swift
struct ReducedMotionView: View {
@Environment(\.accessibilityReduceMotion) var reduceMotion
@State private var isAnimating = false
var body: some View {
Circle()
.fill(Color.blue)
.frame(width: 100, height: 100)
.scaleEffect(isAnimating ? 1.5 : 1.0)
.animation(reduceMotion ? nil : .spring(), value: isAnimating)
.onTapGesture {
isAnimating.toggle()
}
}
}High Contrast
Support Dark Mode
swift
struct HighContrastView: View {
@Environment(\.accessibilityReduceTransparency) var reduceTransparency
@Environment(\.colorScheme) var colorScheme
var body: some View {
ZStack {
if reduceTransparency {
Color.black
} else {
Color.black.opacity(0.8)
}
Text("High Contrast Content")
.foregroundColor(.white)
.padding()
}
.cornerRadius(reduceTransparency ? 0 : 20)
}
}Best Practices
- 1Labels: Provide clear, descriptive labels
- 2Traits: Use appropriate accessibility traits
- 3Focus: Manage accessibility focus
- 4Testing: Test with VoiceOver
- 5Feedback: Provide meaningful feedback
- 6Language: Support right-to-left languages
- 7Testing: Use Accessibility Inspector
- 8Users: Test with real accessibility users
Accessibility makes your app usable by everyone!