Accessibility & Localization

iOS Development — Production & Publishing

Chapter 2 · Accessibility & Localization

A real app ready to ship needs to genuinely work for users this course hasn't specifically written for yet — VoiceOver users, users who've increased their real system text size, and users in a real, different language. This chapter covers all three, at genuinely low real cost given how much SwiftUI already does by default.

VoiceOver: .accessibilityLabel() and .accessibilityHint()

A real Text view is already read aloud by VoiceOver automatically. An icon-only Button, though, has nothing real for VoiceOver to announce on its own:

Button { isShowingNewTask = true } label: { Image(systemName: "plus.circle.fill") } .accessibilityLabel("Add Task") .accessibilityHint("Opens a form to create a new task")
ModifierReal Role
.accessibilityLabel()What VoiceOver actually announces for this element — real, essential for anything with no visible real text of its own.
.accessibilityHint()An optional, real, brief description of what happens after activating it — genuinely supplementary, not a replacement for the label.

A Real, Genuinely Free Default: Dynamic Type

Already Working, By Real Default
Every real SwiftUI built-in text style — .font(.body), .font(.headline), .font(.largeTitle) — automatically scales with the user's own real Dynamic Type setting, with genuinely zero extra code. Every Text view in TaskFlow since Fundamentals Chapter 5 has already had this real, built-in support the whole time.
A Real, Common Way to Accidentally Break It
.font(.system(size: 17)) — a real, fixed point size — genuinely does not scale with Dynamic Type. Reaching for a real, built-in text style instead of a hardcoded size is what keeps this real accessibility support intact; it isn't automatic once a fixed size is used instead.

Grouping with .accessibilityElement(children:)

A TaskRow with several separate real subviews (title, priority badge) is, by real default, announced by VoiceOver as several separate real elements — genuinely tedious to navigate one row at a time. Combining them into one real, coherent announcement:

HStack { Text(task.title) Text(priorityLabel) } .accessibilityElement(children: .combine)

.combine merges every real child's own accessibility content into one real, single announcement — "Buy milk, Medium," read once, rather than as two separate, disjointed real stops.

Localization: Xcode's Real String Catalog

Introduced with Xcode 15, 2023, the real String Catalog (.xcstrings) is the current, recommended way to localize an app — replacing the real, older .strings/.stringsdict file pair.

Real Automatic Extraction

Xcode scans real, existing Text("...") and String(localized:) calls and populates the catalog automatically.

One Real File Per Project

Every language's own real translation lives in one .xcstrings file, browsable in Xcode's own dedicated editor.

Real, Built-In Pluralization

Genuine plural variations (0/1/few/many) are configured per string, no separate .stringsdict file needed.

Text("Add Task") // real, automatically picked up into Localizable.xcstrings let count = tasks.count Text("\(count) tasks remaining") // a real, genuine pluralization candidate
A Real, Practical Workflow
Write real, ordinary English strings directly in SwiftUI code as normal — Xcode's own real "Export Localizations" feature (Product → Export Localizations) generates a real, complete file ready to hand to a translator, and importing their finished work populates every real matching entry in the catalog automatically.

Hands-On Exercises

Exercise 1

Add a real .accessibilityLabel() and .accessibilityHint() to TaskFlow's own icon-only "Add" toolbar button, and explain, in your own words, why an icon-only button genuinely needs a label while a Text("Add") button would not.

📄 View solution
Exercise 2

Apply .accessibilityElement(children: .combine) to TaskFlow's own TaskRow, combining its title and priority badge into one real, single VoiceOver announcement.

📄 View solution
Exercise 3

Explain, in your own words, why using .font(.body) instead of .font(.system(size: 17)) throughout TaskFlow is what actually determines whether Dynamic Type genuinely works, even though both might display at a similar real size by default.

📄 View solution

Chapter 2 Quick Reference

  • .accessibilityLabel()/.accessibilityHint() — real, essential for any element with no visible real text of its own, like an icon-only button
  • Dynamic Type works automatically with every real built-in text style — genuinely broken only by a hardcoded fixed point size
  • .accessibilityElement(children: .combine) — merges several real subviews into one coherent VoiceOver announcement
  • String Catalogs (.xcstrings, Xcode 15/2023) — the real, current localization approach, with automatic string extraction and real, built-in pluralization
  • Product → Export/Import Localizations — the real, practical workflow for handing strings to a translator and bringing their work back in