Module 6 · Lesson 2 advanced

Accessibility & Localization

Mario 15 min read

I am going to say something that might sound harsh but needs to be said: if your app is not accessible, it is not finished. I do not care how polished the UI is, how fast it launches, or how clever the architecture is. If a blind user cannot navigate it with VoiceOver, if a user with low vision cannot read it with Dynamic Type cranked up, if a user with motor impairments cannot tap your tiny buttons — you have shipped an incomplete product.

That used to be an understandable trade-off. Accessibility was hard. You had to know dozens of modifiers, understand the accessibility tree, test with VoiceOver (which has a learning curve of its own), and handle edge cases for every view. Most indie developers simply did not have the time.

AI changes that equation completely. And I mean completely.

Why AI Makes Accessibility Dramatically Easier

Here is the thing about accessibility in SwiftUI: it is almost entirely about knowing the right modifiers and applying them consistently. VoiceOver labels, accessibility hints, trait specifications, grouping, ordering — these are patterns. And AI knows every single one of them.

Before AI, adding accessibility to a view meant:

  1. Reading Apple’s accessibility documentation (scattered across multiple pages)
  2. Figuring out which modifiers apply to your specific view
  3. Writing meaningful labels (harder than it sounds)
  4. Testing with VoiceOver (time-consuming)
  5. Fixing the issues you find (often fiddly)

With AI, steps 1 through 3 collapse into a single prompt. Steps 4 and 5 still require you — AI cannot run VoiceOver for you. But the heavy lifting of knowing what to add and writing it correctly? That is exactly what AI excels at.

VoiceOver: Labels and Hints

VoiceOver is Apple’s screen reader. It reads the interface aloud to users who cannot see the screen. Every interactive element needs a label (what it is) and optionally a hint (what it does).

SwiftUI provides some labels automatically. A Text("Settings") is already accessible — VoiceOver reads “Settings.” A Button("Delete") reads “Delete, button.” But custom views, icon-only buttons, and complex layouts need explicit labels.

Here is a common scenario. You have a row in a list with multiple pieces of information:

struct ExpenseRow: View {
    let expense: Expense

    var body: some View {
        HStack {
            Image(systemName: expense.category.icon)
                .foregroundStyle(expense.category.color)

            VStack(alignment: .leading) {
                Text(expense.name)
                    .font(.body)
                Text(expense.date.formatted(date: .abbreviated, time: .omitted))
                    .font(.caption)
                    .foregroundStyle(.secondary)
            }

            Spacer()

            Text(expense.amount, format: .currency(code: "USD"))
                .font(.body)
                .monospacedDigit()
        }
    }
}

Without accessibility modifications, VoiceOver reads each element separately: “Food icon.” Pause. “Groceries.” Pause. “February 15, 2026.” Pause. “47 dollars and 82 cents.” That is four separate swipe gestures to understand one row.

Here is what it should be:

struct ExpenseRow: View {
    let expense: Expense

    var body: some View {
        HStack {
            Image(systemName: expense.category.icon)
                .foregroundStyle(expense.category.color)

            VStack(alignment: .leading) {
                Text(expense.name)
                    .font(.body)
                Text(expense.date.formatted(date: .abbreviated, time: .omitted))
                    .font(.caption)
                    .foregroundStyle(.secondary)
            }

            Spacer()

            Text(expense.amount, format: .currency(code: "USD"))
                .font(.body)
                .monospacedDigit()
        }
        .accessibilityElement(children: .combine)
        .accessibilityLabel("\(expense.name), \(expense.amount.formatted(.currency(code: "USD"))), \(expense.category.name)")
        .accessibilityHint("Double tap to view details")
    }
}

Now VoiceOver reads the entire row as one element: “Groceries, $47.82, Food. Double tap to view details.” One swipe. Clear. Usable.

This is the exact kind of task where AI shines. Here is the prompt:

Review ExpenseRow.swift and add VoiceOver accessibility.
The row should be read as a single element combining the
expense name, amount, and category. Include an accessibility
hint for the tap action. Make sure the icon is not read
separately.

AI will add the exact modifiers needed. It knows .accessibilityElement(children: .combine), it knows .accessibilityLabel, it knows .accessibilityHint. You just need to tell it what the user experience should be.

The AI Accessibility Audit

This is one of my favorite uses of AI. Take any view in your project and ask:

Review TransactionDetailView.swift for accessibility issues.
Check for:
1. Missing VoiceOver labels on interactive elements
2. Images without accessibility descriptions
3. Custom views that VoiceOver cannot navigate
4. Incorrect accessibility traits
5. Elements that should be grouped but are not
6. Tap targets smaller than 44x44 points

AI will return a detailed list of issues with specific fixes. In my experience, it catches 80-90% of accessibility problems on the first pass. The remaining issues usually require manual VoiceOver testing to find — things like navigation order being confusing, or a label that is technically correct but does not make sense when read aloud.

Let me give you a real example. I ran this audit on a view from one of our apps and AI found:

  • An icon-only button with no accessibility label (VoiceOver just said “button”)
  • A chart view that was completely invisible to VoiceOver
  • A color-coded status indicator (red/yellow/green) with no text alternative
  • A custom slider that did not report its value to VoiceOver
  • A decorative background image that VoiceOver was announcing unnecessarily

Five issues. All fixable in under 10 minutes with AI-generated code. Without AI, finding these would have taken me an hour of manual VoiceOver testing, and fixing them would have required research into the specific accessibility modifiers for each case.

Dynamic Type

Dynamic Type is Apple’s system for letting users choose their preferred text size. Some users set it to the smallest size. Some set it to the largest. Some use the even larger Accessibility sizes. Your app needs to handle all of them.

The good news: if you use SwiftUI’s built-in text styles (.font(.body), .font(.title), .font(.caption)), Dynamic Type support is automatic. The text scales with the user’s preference.

The bad news: your layout probably breaks at larger sizes.

Here is what happens. You have a beautiful row with text on the left and a price on the right. At the default text size, it fits perfectly. At the largest Accessibility text size, the text overflows, overlaps, or gets truncated. The layout was designed for one size and it cannot flex.

// This breaks at large Dynamic Type sizes
HStack {
    Text("Monthly subscription to premium features")
    Spacer()
    Text("$9.99")
}

At XXXL accessibility size, the left text wraps to four lines and pushes the price off screen.

The fix is to adapt the layout:

// Adapts to Dynamic Type size
@Environment(\.dynamicTypeSize) var dynamicTypeSize

var body: some View {
    if dynamicTypeSize.isAccessibilitySize {
        VStack(alignment: .leading) {
            Text("Monthly subscription to premium features")
            Text("$9.99")
                .font(.body.bold())
        }
    } else {
        HStack {
            Text("Monthly subscription to premium features")
            Spacer()
            Text("$9.99")
        }
    }
}

Or even better, use ViewThatFits:

ViewThatFits {
    HStack {
        Text("Monthly subscription to premium features")
        Spacer()
        Text("$9.99")
    }
    VStack(alignment: .leading) {
        Text("Monthly subscription to premium features")
        Text("$9.99")
            .font(.body.bold())
    }
}

Ask AI to handle this across your app:

Review all views in the Views/ directory for Dynamic Type
support. Identify layouts that will break at the largest
Accessibility text sizes (AX1 through AX5). Suggest fixes
using ViewThatFits or @Environment(\.dynamicTypeSize) for
adaptive layouts.

This is tedious work that AI makes trivial. It can scan every view, identify rigid HStack layouts that will overflow, and suggest adaptive alternatives. That is hours of manual work reduced to a single prompt and review cycle.

Color Contrast

Color contrast is straightforward in principle but easy to get wrong. Text needs to have sufficient contrast against its background. The WCAG 2.1 standard requires a contrast ratio of at least 4.5:1 for normal text and 3:1 for large text.

If you are using SwiftUI’s semantic colors — Color.primary, Color.secondary, .foregroundStyle(.primary) — you are mostly fine. Apple designed these colors to meet contrast requirements in both light and dark mode.

Where it goes wrong is custom colors. That beautiful light gray text on a white background? Fails contrast in light mode. That vibrant blue on a dark navy background? Fails in dark mode.

Ask AI to check:

Review our custom color definitions in Assets.xcassets and
our Color extension. Check the contrast ratio of every
text-on-background combination in our app. Flag any that
fail WCAG 2.1 AA contrast requirements (4.5:1 for body
text, 3:1 for large text). Suggest alternative colors
that meet the standard while staying close to our design.

AI knows the contrast formulas and can calculate ratios. It cannot see your rendered app, but it can analyze your color definitions and flag potential issues. You still need to visually verify, but the analysis is instant.

One more thing about color: never use color as the only way to convey information. A red/green status indicator means nothing to someone who is colorblind. Add a shape, an icon, or text.

// Bad — color is the only indicator
Circle()
    .fill(isActive ? .green : .red)

// Good — color plus icon
HStack {
    Image(systemName: isActive ? "checkmark.circle.fill" : "xmark.circle.fill")
    Text(isActive ? "Active" : "Inactive")
}
.foregroundStyle(isActive ? .green : .red)

String Catalogs and Localization

Let us shift to localization. Xcode 15 introduced String Catalogs (.xcstrings files), and they are a massive improvement over the old .strings files. If your app is English-only today but might expand later, setting up String Catalogs now costs almost nothing and saves enormous effort later.

Here is the basic workflow:

Step 1: In Xcode, add a String Catalog file to your project (File > New > String Catalog). Xcode automatically extracts all your literal strings from SwiftUI views.

Step 2: Any Text("string") in your code is automatically localizable. No more NSLocalizedString or LocalizedStringKey wrappers — SwiftUI handles it.

Step 3: Add languages in your project settings. Open the String Catalog and translate.

Where AI helps enormously:

Generate Spanish translations for all strings in our
String Catalog. Here is the current list of English strings:
[paste the keys or export the catalog]

Use Latin American Spanish, neutral tone, consistent with
Apple's platform terminology (e.g., "Ajustes" not
"Configuraciones" for Settings).

AI will generate translations that are surprisingly good. Not perfect — you should have a native speaker review them — but good enough for a first pass. And it knows platform-specific terminology, which machine translation services often get wrong.

Pluralization Rules

English is simple: “1 item” or “2 items.” Other languages have complex pluralization rules. Arabic has six plural forms. Russian has three. String Catalogs handle this with a built-in pluralization system.

In your code:

Text("^[\(count) item](inflect: true)")

This tells SwiftUI to handle pluralization automatically. But for custom strings in other languages, you need explicit plural rules in the String Catalog.

Ask AI to help:

I need to add German pluralization rules for these strings
in our String Catalog:
- "%lld expense" / "%lld expenses"
- "%lld category" / "%lld categories"
- "%lld day remaining" / "%lld days remaining"

German has two plural forms (one, other). Provide the
correct String Catalog entries.

AI knows pluralization rules for dozens of languages. This is exactly the kind of specialized knowledge that makes AI invaluable — how many developers have German pluralization rules memorized?

RTL Layout Support

If you support Arabic, Hebrew, or other right-to-left languages, your entire layout needs to mirror. The good news: SwiftUI does most of this automatically. HStack reverses. leading becomes trailing. Alignment flips.

The bad news: custom layouts, hardcoded padding values, and asymmetric designs will not flip automatically.

// Bad — hardcoded leading padding that does not flip
.padding(.leading, 16)

// Good — uses .leading which SwiftUI flips automatically
// (this is actually the same, .leading DOES flip)
// The issue is usually with custom offsets:
.offset(x: 20) // This does NOT flip for RTL

// Fix
@Environment(\.layoutDirection) var layoutDirection
.offset(x: layoutDirection == .rightToLeft ? -20 : 20)

Ask AI to review your app for RTL issues:

Review all views for RTL layout compatibility. Look for:
1. Hardcoded .offset(x:) values that should flip for RTL
2. Custom drawing code that assumes left-to-right
3. Asymmetric padding or margins
4. Icons that have directional meaning (arrows, etc.)

Why Accessibility Is Not Optional

I want to close this section with something that goes beyond technical implementation.

In many countries, digital accessibility is a legal requirement. The European Accessibility Act, which takes full effect in 2025, requires apps sold in the EU to be accessible. The Americans with Disabilities Act has been applied to mobile apps in court cases. This is not theoretical — companies have been sued over inaccessible apps.

But forget the legal argument. Here is the real one.

Roughly 15% of the world’s population lives with some form of disability. That is over a billion people. Many of them use iPhones. Many of them would love to use your app. And Apple has built the most comprehensive accessibility framework in the industry — VoiceOver, Switch Control, Voice Control, Dynamic Type, Reduce Motion, all baked into every device.

The tools are there. The framework is there. And now, with AI, the knowledge gap is gone too. You do not need to memorize every accessibility modifier. You just need to ask AI to add them and then verify with VoiceOver that the experience makes sense.

There is no excuse left.

Accessibility and Localization Checklist

Run through this before submitting to the App Store:

CheckHow to Verify
All interactive elements have VoiceOver labelsAI audit + manual VoiceOver test
All images have accessibility descriptionsAI audit
Custom views are properly grouped for VoiceOverManual VoiceOver navigation
Dynamic Type works at all sizes including Accessibility sizesSettings > Accessibility > Display & Text Size > Larger Text
No information conveyed by color aloneVisual review
Minimum tap target 44x44 pointsAI review of frame sizes
Text contrast meets WCAG 2.1 AA (4.5:1 body, 3:1 large)AI color analysis + manual check
All user-facing strings are localizableString Catalog extraction (Xcode does this automatically)
Pluralization rules are correct for supported languagesTest with different quantities in each language
RTL layout works correctly (if supporting RTL languages)Toggle language to Arabic in simulator

Closing

Accessibility and localization used to be the features that indie developers skipped because they were too time-consuming. AI eliminates that excuse. A single audit prompt finds most accessibility issues. A single translation prompt generates a first draft for any language. The knowledge barrier that made this work so daunting — memorizing dozens of modifiers, understanding pluralization rules, knowing contrast ratios — that barrier is gone.

You still need to test. VoiceOver testing is not optional and AI cannot do it for you. But the implementation work? AI handles the vast majority of it.

Make your app usable by everyone. It is easier than it has ever been, and it is the right thing to do.

In the next and final lesson, we bring it all together — App Store submission, launch preparation, and the end of this course.


Key Takeaways

  1. Accessibility is not optional — it is a legal requirement in many jurisdictions and the right thing to do for over a billion users with disabilities
  2. AI knows every accessibility modifier — use it to add VoiceOver labels, hints, traits, and grouping to your views
  3. The AI accessibility audit is one of the highest-value prompts you can run — catches 80-90% of issues in one pass
  4. Dynamic Type breaks layouts — use ViewThatFits or @Environment(\.dynamicTypeSize) for adaptive designs
  5. Never use color alone to convey information — always pair with icons, shapes, or text
  6. String Catalogs make localization dramatically simpler than the old .strings approach
  7. AI generates translations that are good first drafts — always have a native speaker review
  8. Pluralization is complex in many languages — AI knows the rules for dozens of languages
  9. RTL support is mostly automatic in SwiftUI but watch for hardcoded offsets and custom drawing
  10. Always test with VoiceOver manually — AI can write the code, but only you can verify the experience

Homework

Accessibility audit exercise (30 minutes):

  1. Run the AI accessibility audit prompt on your app’s three most complex views
  2. Implement all suggested fixes
  3. Turn on VoiceOver on your device or simulator (Settings > Accessibility > VoiceOver) and navigate your entire app using only VoiceOver
  4. Note any issues the AI missed — confusing navigation order, labels that sound wrong when spoken aloud, interactive elements that VoiceOver skips
  5. Fix those issues and run the audit again

Localization exercise (20 minutes):

  1. Add a String Catalog to your project if you do not have one already
  2. Pick a second language (any language you can verify or have a friend verify)
  3. Ask AI to generate translations for all your user-facing strings
  4. Test the app in that language — check for text truncation, layout overflow, and incorrect translations
  5. If the language is RTL, verify layout mirroring
M

Mario

Founder & CEO

Founder of NativeFirst. Building native Apple apps with SwiftUI and a passion for great user experiences.

Comments

Leave a comment

0/1000