Accessibility is often treated as a compliance checkbox — something you add at the end of a project because someone in legal asked for it. That framing misses why it matters.
Approximately 15% of the world’s population lives with some form of disability. In the context of an iOS app, that means users who are blind or have low vision and use VoiceOver to navigate by touch and hearing. It means users with motor impairments who use Switch Control or Voice Control instead of tapping a screen. It means users who are elderly and have increased their device text size substantially. It means users in a bright kitchen trying to read a screen in sunlight.
If your app only works well for users with perfect vision and fine motor control, you’ve built something that excludes a huge portion of the people who might benefit from it. For a smart home app specifically — which is supposed to make daily life more manageable — that exclusion is a real failure.
The good news is that SwiftUI makes basic accessibility almost free. Invest a small amount of deliberate effort and you can support all of these users well.
There are three main areas to address.
VoiceOver — Apple’s screen reader for blind and low-vision users. It reads the contents of the screen aloud and lets users navigate using gestures. For VoiceOver to work well, every interactive element needs a clear, meaningful label. SwiftUI reads text labels automatically, but icon-only buttons are silent without explicit annotation.
Dynamic Type — iOS allows users to set their preferred text size, from extra small to accessibility sizes that are several times larger than default. Apps that use semantic font styles (.headline, .body, .caption) scale automatically. Apps that use fixed sizes (.font(.system(size: 14))) stay small regardless of user preference.
Voice Control — lets users navigate entirely by speaking. It works by matching spoken words to the visible text of interactive elements. “Tap Turn On” works automatically if a button shows “Turn On”. Icon buttons that show nothing need explicit labels so users can say “Tap Settings” rather than “Tap 7”.
What’s already working
SwiftUI’s Text views are readable by VoiceOver by default. Buttons with text labels are fully addressable by Voice Control without any extra work. The entire mode grid in the oven screen (“Bake”, “Broil High”, “Convect”) and all cycle tiles in the dishwasher screen work with Voice Control out of the box because they have visible text labels.
What needs explicit labels
Icon-only buttons throughout the app need .accessibilityLabel:
// OvenDetailView toolbar
Button { showSettings = true } label: {
Image(systemName: "gearshape")
.foregroundColor(AppTheme.accent)
}
.accessibilityLabel("Device settings")
// DashboardView toolbar — connection indicator isn't a button,
// but should describe its state to VoiceOver
HStack {
Circle().fill(devices.isWebSocketConnected ? AppTheme.success : AppTheme.textSecondary)
Text(devices.isWebSocketConnected ? "Connected" : "Connecting…")
}
.accessibilityElement(children: .combine)
.accessibilityLabel(devices.isWebSocketConnected ? "Connected to server" : "Connecting to server")
Temperature stepper buttons need labels and hints:
Button { pendingTemp = OvenPresenter.clampedTemperature(pendingTemp, delta: -25) } label: {
Image(systemName: "minus")
}
.accessibilityLabel("Decrease temperature")
.accessibilityHint("Decreases by 25 degrees")
Button { pendingTemp = OvenPresenter.clampedTemperature(pendingTemp, delta: +25) } label: {
Image(systemName: "plus")
}
.accessibilityLabel("Increase temperature")
.accessibilityHint("Increases by 25 degrees")
The temperature arc should expose its value rather than reading as a decorative graphic:
TemperatureArcView(current: Double(state.currentTemperature), ...)
.accessibilityLabel("Current temperature")
.accessibilityValue("\(state.currentTemperature) degrees. Target is \(state.targetTemperature) degrees.")
The power outage banner on the dashboard deserves an urgency trait so VoiceOver announces it immediately:
PowerOutageBanner(deviceName: device.name)
.accessibilityAddTraits(.isStaticText)
.accessibilityLabel("Warning: \(device.name) has lost power. Food may spoil.")
Dynamic Type
The app currently uses fixed font sizes everywhere (.font(.system(size: 15))). Transitioning to semantic styles where the layout allows lets the app honour the user’s size preference:
// Current — fixed
Text(device.name).font(.system(size: 16, weight: .semibold))
// Accessible — scales with user preference
Text(device.name).font(.headline)
For controls where a fixed size is intentional (small badge text inside cards), you can clamp the Dynamic Type range without breaking layout:
Text(level.uppercased())
.font(.system(size: 9, weight: .semibold))
.dynamicTypeSize(.small ... .accessibility1)
Testing accessibility
The fastest way to audit the app is with Xcode’s Accessibility Inspector (Xcode → Open Developer Tool → Accessibility Inspector). It shows every element’s label, value, and traits, and flags missing labels without you having to enable VoiceOver on a device.
For Voice Control, the Environment Overrides panel in the debugger can enable it in the simulator without going through Settings. Running through the key flows — turning the oven on, setting a temperature, starting a dishwasher cycle — confirms that every interactive element is addressable by voice.
Accessibility isn’t a feature. It’s a quality bar. An app that works for everyone is simply a better app.


Leave a Reply
You must be logged in to post a comment.