All skills
avdlee avatar

/swiftui-expert-skill

@1e522cf

Use when writing, reviewing, or refactoring SwiftUI code for iOS or macOS, including state and `@Observable` data flow, view composition, resizable layouts, safe areas, display scale, performance, lists, environment, localization, animation, Liquid Glass, and API migration. Also use for iPhone Duo, foldable, or large-display layouts (`NavigationSplitView` on large displays, two-column reflow, foldable grids, `ArrangementView`, `ReservedRegion`), hinge effects, vertical bars, `@State` initialization or synthesized-property diagnostics, `@ContentBuilder` ambiguity, `reorderable` drag/drop, custom `AsyncImage` `URLSession`, swipe actions outside List, item-bound `alert`/`confirmationDialog`, `ToolbarOverflowMenu`, `AnimatableValues`, Document APIs (`Document`/`DocumentReader`), and Instruments `.trace` capture or analysis.

Use this Skill: https://skilld.dev/gh/avdlee/swiftui-agent-skill/swiftui-expert-skill

This session only. Nothing lands on disk.

referenceslocalization.md

≈2.5k tokens on demand. Your agent reads this file only when SKILL.md points to it.

SwiftUI Localization Reference

Guidance for user-facing text: Text, Button, Label, navigation/toolbar titles, alerts, and types that carry localizable strings. For the narrower "verbatim vs localized" decision on a single Text, see references/text-patterns.md.

Table of Contents

SwiftUI Localizes String Literals Automatically

Initializers that accept LocalizedStringKey (Text, Button, Label, .navigationTitle, alert titles, and so on) treat string literals as localization keys automatically. Do not wrap literals in NSLocalizedString, String(localized:), or LocalizedStringResource — that resolves the string eagerly and ignores \.locale overrides.

// AVOID: double work, and resolves eagerly
Text(String(localized: "start_workout"))

// PREFER: pass the literal directly
Text("start_workout")

Both opaque keys ("start_workout") and natural-language strings ("Start Workout") work as keys — pick whichever convention the project already uses. Use Text(verbatim:) only to opt a literal out of localization (e.g. a debug label interpolating a runtime value). When the argument is already a String variable, Text(value) calls the StringProtocol overload and skips localization on its own.

String Catalogs

Most projects localize through String Catalogs (.xcstrings). Each build syncs new keys from code into the catalog, but the catalog file must already exist — Xcode doesn't create one automatically. If a project already uses .strings / .stringsdict, add new strings there rather than migrating. Route groups of strings to a specific catalog with tableName:.

Text("Explore", tableName: "Navigation",
     comment: "Tab bar item title for the Explore screen.")

Bundle for Swift Packages and Frameworks

Apps, app extensions, and XPC services are their own main bundle, so bundle can be omitted. Frameworks and Swift packages need an explicit bundle: — without one, SwiftUI looks up strings in Bundle.main, the lookup fails silently, and the string appears unlocalized at runtime.

// AVOID (inside a framework/package): searches the app's catalog
Text("Save to Favorites")

// PREFER: #bundle resolves to the current target's bundle
Text("Save to Favorites", bundle: #bundle,
     comment: "Button to bookmark a recipe.")

#bundle is the preferred form; Bundle.module and Bundle(for:) still work but are older patterns.

Localizing Variables and Custom Types

A String variable passed to Text runs the StringProtocol overload and is not localized. Wrapping it in LocalizedStringKey(_:) doesn't help — Xcode can't extract a literal from a runtime value, so nothing lands in the catalog. To localize a value chosen from a known set, model it with a type that exposes LocalizedStringResource:

enum Category {
    case appetizers, mains, desserts
    var name: LocalizedStringResource {
        switch self {
        case .appetizers: "Appetizers"
        case .mains: "Mains"
        case .desserts: "Desserts"
        }
    }
}

Text(category.name)

When a view or model exposes user-facing text, type the property as LocalizedStringKey or LocalizedStringResource rather than String. Every SwiftUI view that takes localized text accepts both, so deferring resolution costs nothing at the display site and preserves locale/bundle context.

LocalizedStringResource for Non-View Types

When a non-view type carries user-facing text — a model object, a tip, a queued notification — use LocalizedStringResource instead of String. It defers resolution to display time, so it honors the locale active when the value actually renders, not when it was created.

// AVOID: resolved at creation time, can't re-render in another locale
struct Tip { let headline: String }
let tip = Tip(headline: String(localized: "Tip of the Day"))

// PREFER: resolution deferred to display time
struct Tip { let headline: LocalizedStringResource }
let tip = Tip(headline: "Tip of the Day")

Apply this when designing new types or changing user-facing text — don't sweep through existing String properties as part of unrelated edits.

Interpolation vs Concatenation

String interpolation preserves LocalizedStringKey and produces a format string in the catalog (e.g. "Welcome, %@"). Concatenation with + produces a plain String and is not localized. Never glue separately localized fragments into a sentence — word order varies across languages.

// AVOID: + produces String; sentence assembly breaks word order
Text("Error: " + statusMessage)
Text(String(localized: "Created by")) + Text(" ") + Text(authorName)

// PREFER: one interpolated string translators can rearrange
Text("Error: \(statusMessage)")
Text("Created by \(authorName)")

Casing

Bake the desired case into the string rather than transforming at runtime via .textCase(_:), .localizedUppercase, or .localizedCapitalized. A runtime transform forces the same casing on every translation, leaving translators no room to adjust per language.

// AVOID
Text("Section Header").textCase(.uppercase)

// PREFER
Text("SECTION HEADER")

This applies to localized strings; display user-entered text as-is. If a transform is unavoidable, prefer .localizedUppercase / .localizedCapitalized, which honor the user's locale.

Formatting Dates, Numbers, and Currencies

Use Text's format: parameter or .formatted() instead of DateFormatter / NumberFormatter with hardcoded format strings. Format styles adapt to the user's locale; hardcoded format strings don't.

// AVOID
let f = DateFormatter(); f.dateFormat = "MM/dd/yyyy"
Text(f.string(from: workout.date))
Text("$\(product.price, specifier: "%.2f")")

// PREFER
Text(workout.date, format: .dateTime.month().day().year())
Text(product.price, format: .currency(code: store.currencyCode))

Field components (.month(), .day()) choose which fields appear; the locale decides the order. For lists, Array.formatted() inserts locale-correct separators and conjunctions instead of joined(separator:). When DateFormatter is genuinely unavoidable, use setLocalizedDateFormatFromTemplate(_:) rather than assigning dateFormat.

Layout for Localization

  • Use .leading / .trailing instead of .left / .right — they flip for right-to-left locales.
  • Don't hardcode frame widths/heights for text; translations vary in length and scripts vary in height. Use ViewThatFits when a layout might not fit longer translations.
  • Use text styles (.body, .headline) rather than fixed point sizes, so line height adapts per script.
// PREFER
Text(recipe.title)
    .frame(maxWidth: .infinity, alignment: .leading)

ViewThatFits {
    HStack { actionButtons }
    VStack { actionButtons }
}

Reading the Current Locale

Use @Environment(\.locale) for locale-dependent logic in views, not Locale.current — the environment respects preview overrides and per-view injection.

String(localized:) Outside SwiftUI Views

When you need a localized String outside a view, use String(localized:), not NSLocalizedString. Don't interpolate inside NSLocalizedString — Xcode extracts keys from literals at build time and can't extract interpolated values. String(localized:) supports interpolation (it extracts the format string and treats values as runtime arguments) and is preferred over String(format:), which always renders digits as 0–9 regardless of locale.

// PREFER
let title = String(localized: "activity_summary", comment: "Dashboard header")

Comments for Translators

Add a comment: describing the UI element and its purpose, especially for ambiguous strings. For interpolated strings, describe each placeholder by position — translators don't see Swift variable names. Comments can live at the call site or in the String Catalog's per-string Comment field; keep one source of truth per string.

// AVOID: "Edit" could be a noun or a verb
Text("Edit")

// PREFER
Text("Edit", comment: "Toolbar button that enters editing mode for the list.")
Text("Completed \(count) of \(total)",
     comment: "Progress label — first variable is finished items, second is the total.")

Summary Checklist

  • String literals passed directly to Text/Button/Label (not wrapped in NSLocalizedString/String(localized:))
  • bundle: #bundle on user-facing strings inside frameworks and Swift packages
  • User-facing text on models/non-view types typed as LocalizedStringResource, not String
  • Interpolation (not +) for dynamic strings; no sentence assembly from fragments
  • Case baked into the string, not applied via .textCase
  • Dates/numbers/currencies use format: / .formatted() with locale-aware styles
  • .leading/.trailing (not .left/.right); no hardcoded text frame sizes
  • @Environment(\.locale) for locale logic in views
  • comment: provided for ambiguous strings and interpolated placeholders

Source: SKILL.md on GitHub

No alerts2d5 checks · Risk SAFE
  • Gen Agent Trust Hub2d

    The skill is a professional-grade assistant for SwiftUI development and performance profiling. It includes Python scripts to interface with the Xcode xctrace CLI for recording and analyzing Instruments traces. All identified code and instructions are consistent with its stated purpose and follow security best practices.

  • Socket2d

    No alerts

  • Snyk2d

    Risk: LOW · No issues

  • Runlayer6mo

    19 files scanned · No issues

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at 1e522cf. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub yesterday.

Activeupdated 3 days ago
  • Performance
  • swiftui
  • ios
  • macos
  • instruments
  • state-management
  • view-composition
  • accessibility
  • animations

README badge

README badge for avdlee/swiftui-agent-skill/swiftui-expert-skill

Provides guidance for writing, reviewing, and refactoring SwiftUI code for iOS and macOS, including state management, view composition, performance optimization, and Instruments trace recording and analysis. Covers deprecated API detection, animation patterns, accessibility, and Liquid Glass adoption.

Generated from the current SKILL.md.

Does this skill work with both iOS and macOS?
Yes. The skill covers SwiftUI for both iOS and macOS, with dedicated reference sections for macOS-specific patterns like scenes, window styling, and views (HSplitView, Table, PasteButton).
Can this skill help me record and analyze Instruments traces?
Yes. The skill includes workflows to record traces via `record_trace.py` (with template selection for real devices vs simulators) and analyze them via `analyze_trace.py` to identify hangs, hitches, CPU hotspots, and excessive view updates.
Does this skill enforce a specific architecture pattern?
No. It focuses on correctness and performance without mandating MVVM, VIPER, or other architectural styles, though it encourages separating business logic from views for testability.
What does the skill do about deprecated APIs?
It consults `references/latest-apis.md` at the start of every task to identify and replace deprecated APIs with modern equivalents across iOS 15+ through iOS 26+, and gates version-specific APIs with `#available`.
Does this skill handle Liquid Glass effects?
Yes, but only when explicitly requested by the user. It includes guidance in `references/liquid-glass.md` for iOS 26+ Liquid Glass adoption with sensible fallbacks for earlier versions.

Generated from the current SKILL.md. These answers refresh after source changes.