AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Ios Rules

skill-abdullah4ai-apple-developer-toolkit-ios-rules · by Abdullah4AI

38 battle-tested iOS development rules covering accessibility, navigation, architecture, dark mode, localization, App Review guidelines, and more. Targets the mistakes LLMs actually make when generating Swift/SwiftUI code.

No reviews yet
0 installs
27 views
0.0% view→install

Install

$ agentstack add skill-abdullah4ai-apple-developer-toolkit-ios-rules

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access Used
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-abdullah4ai-apple-developer-toolkit-ios-rules)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
2mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Ios Rules? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

iOS Development Rules

38 rules for writing production-quality iOS apps. Each rule targets common LLM mistakes with concrete fixes.

Accessibility

REDUCE MOTION:

  • Check: @Environment(\.accessibilityReduceMotion) var reduceMotion
  • When enabled:
  • Replace .spring() with .easeInOut(duration: 0.2)
  • Replace slide transitions with .opacity
  • Disable auto-playing animations
  • Keep functional animations (progress bars), remove decorative ones
withAnimation(reduceMotion ? .easeInOut(duration: 0.2) : .spring(response: 0.3)) {
    // state change
}
.transition(reduceMotion ? .opacity : .slide)

REDUCE TRANSPARENCY:

  • Check: @Environment(\.accessibilityReduceTransparency) var reduceTransparency
  • When enabled: use opaque backgrounds instead of materials/blur.
.background(reduceTransparency ? Color(AppTheme.Colors.surface) : .ultraThinMaterial)

VOICEOVER LABELS:

  • All interactive elements: .accessibilityLabel("descriptive text").
  • Non-obvious actions: .accessibilityHint("Double tap to delete this item").
  • Decorative images: .accessibilityHidden(true).
  • Informative images: .accessibilityLabel("Profile photo of John").
  • Icon-only buttons: MUST have .accessibilityLabel().
Button(action: addItem) {
    Image(systemName: "plus")
}
.accessibilityLabel("Add new item")

GROUPING & COMBINING:

  • Related content (icon + label + value): .accessibilityElement(children: .combine).
  • Custom read order: .accessibilityElement(children: .ignore) + manual .accessibilityLabel.
  • Cards with multiple elements: combine into single accessible element.
HStack {
    Image(systemName: "heart.fill")
    Text("Favorites")
    Spacer()
    Text("12")
}
.accessibilityElement(children: .combine)

ACCESSIBILITY TRAITS:

  • Section headers: .accessibilityAddTraits(.isHeader)
  • Buttons that play media: .accessibilityAddTraits(.startsMediaSession)
  • Summary/aggregate values: .accessibilityAddTraits(.isSummaryElement)
  • Selected items: .accessibilityAddTraits(.isSelected)

FOCUS MANAGEMENT:

  • Use @FocusState with field enum for form navigation.
  • .submitLabel(.next) to show "Next" on keyboard, .submitLabel(.done) for last field.
  • Chain fields with .onSubmit { focusedField = .nextField }.
enum Field: Hashable { case name, email, password }
@FocusState private var focusedField: Field?

TextField("Name", text: $name)
    .focused($focusedField, equals: .name)
    .submitLabel(.next)
    .onSubmit { focusedField = .email }

DYNAMIC TYPE:

  • System text styles (.body, .headline, etc.) scale automatically.
  • NEVER use .font(.system(size:)) — it opts out of Dynamic Type.
  • If layout breaks at large sizes: .minimumScaleFactor(0.8) as last resort.
  • Test with Xcode Environment Overrides at the largest accessibility size.
  • ScrollView wraps content that may overflow at large type sizes.

COLOR & CONTRAST:

  • Don't use color alone for status — always pair with icon + text.
  • Minimum 4.5:1 contrast for normal text, 3:1 for large text.
  • .foregroundStyle(.secondary) for de-emphasized text (maintains adaptive contrast).

ACCESSIBLE CUSTOM CONTROLS:

  • Custom sliders/steppers: .accessibilityValue(), .accessibilityAdjustableAction().
  • Custom toggles: .accessibilityAddTraits(.isToggle), .accessibilityValue(isOn ? "on" : "off").
  • Progress indicators: .accessibilityValue("\(Int(progress * 100)) percent").

App Clips

APP CLIPS: SETUP: Requires separate App Clip target (kind: "app_clip" in plan extensions array). App Clips are a lightweight version of your app for quick, focused tasks.

INFO.PLIST (auto-configured on App Clip target in project.yml): NSAppClip dict with NSAppClipRequestEphemeralUserNotification and NSAppClipRequestLocationConfirmation is set automatically. No manual configuration needed.

ASSOCIATED DOMAINS (auto-configured in project.yml entitlements): appclips:{bundleID} and parent-application-identifiers are set automatically.

APP CLIP EXPERIENCE URL: Configure in App Store Connect. Users launch App Clip via NFC, QR code, Maps, etc.

APP CLIP INVOCATION (receive URL): struct AppClipApp: App { var body: some Scene { WindowGroup { ContentView() .onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { activity in guard let url = activity.webpageURL else { return } // Handle URL: extract parameters, show relevant content } } } }

SKOverlay (promote full app from within App Clip): import StoreKit @Environment(\.requestAppStoreOverlay) var requestOverlay Button("Get Full App") { requestOverlay(AppStoreOverlay.AppClipCompletion(appIdentifier: "YOURAPPID")) }

CONSTRAINTS:

  • App Clip binary must be = 5, key task completed)
  • Track with @AppStorage("launchCount"), increment in .onAppear of root view
  • Apple limits to 3 prompts/year — never on first launch
  • Check: never request immediately after error, crash, or purchase

Apple Translation

APPLE ON-DEVICE TRANSLATION (Translation framework): FRAMEWORK: import Translation (iOS 17.4+, on-device, NO internet required, NO API key)

KEY DISTINCTION: Translation is for translating USER CONTENT on demand (e.g. translating a message from French to English). It is NOT for app localization (.strings files). Do not confuse the two.

MODIFIER APPROACH (simplest — shows system translation sheet): @State private var showTranslation = false Text(userContent) .translationPresentation(isPresented: $showTranslation, text: userContent) Button("Translate") { showTranslation = true }

PROGRAMMATIC TRANSLATION (TranslationSession): @State private var translatedText = ""

func translateText(_ input: String) async { let config = TranslationSession.Configuration(source: .init(identifier: "fr"), target: .init(identifier: "en")) let session = TranslationSession(configuration: config) do { let response = try await session.translate(input) translatedText = response.targetText } catch { // Handle: language pair not supported on device, model not downloaded } }

// Call with .task or Button: .task { await translateText(originalText) }

SUPPORTED LANGUAGES: Check Translation.supportedLanguages for the device's available language pairs. AVAILABILITY: Some language pairs require a model download on first use. NO ENTITLEMENTS NEEDED: Translation framework requires no special entitlements or Info.plist keys.

Biometrics

BIOMETRIC AUTHENTICATION (Face ID / Touch ID):

  • import LocalAuthentication; LAContext().evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics)
  • Requires NSFaceIDUsageDescription permission (add CONFIG_CHANGES)
  • Check canEvaluatePolicy first; fall back to passcode if biometrics unavailable
  • LAContext().biometryType to detect .faceID vs .touchID vs .none
  • Always provide manual unlock alternative (PIN/password)

Camera

CAMERA & PHOTOS:

  • PhotosPicker (PhotosUI) for gallery selection — no permissions needed for limited access
  • For camera capture: AVCaptureSession + AVCapturePhotoOutput + UIViewControllerRepresentable wrapper
  • Camera requires NSCameraUsageDescription permission (add CONFIG_CHANGES)
  • Full photo library requires NSPhotoLibraryUsageDescription
  • Use @State private var selectedItem: PhotosPickerItem? with .onChange to load
  • Load image: try await item.loadTransferable(type: Data.self)

Charts

SWIFT CHARTS:

  • import Charts; use Chart { } container
  • BarMark, LineMark, AreaMark, PointMark, RuleMark for data visualization
  • .foregroundStyle(by: .value("Category", item.category)) for color coding
  • chartXAxis { AxisMarks() }, chartYAxis { AxisMarks() } for custom axis labels
  • Extract Chart into a separate computed property to avoid body complexity
  • Use .chartScrollableAxes(.horizontal) for large datasets

Color & Contrast

CONTRAST RATIO REQUIREMENTS (WCAG 2.1 / Apple HIG):

  • Normal text ( 300ms. Instant operations need no indicator.

BADGE/CHIP PATTERN:

Text("Label")
    .font(.caption)
    .fontWeight(.medium)
    .padding(.horizontal, 8)
    .padding(.vertical, 4)
    .background(AppTheme.Colors.primary.opacity(0.15))
    .foregroundStyle(AppTheme.Colors.primary)
    .clipShape(Capsule())

TOGGLE/SWITCH:

  • Use Toggle for binary settings with immediate effect.
  • Label must clearly describe the ON state.
  • Group related toggles in a Section with a header.

PICKER PATTERNS:

  • 2-4 options: Picker with .segmentedStyle.
  • 5+ options: Picker with default menu style or NavigationLink to selection list.
  • Date selection: DatePicker with appropriate displayedComponents.

EMPTY STATES:

  • Always use ContentUnavailableView for empty lists/collections.
  • Include: icon (SF Symbol), title, description, and action button if applicable.
  • Never show a blank screen — empty state guides the user to the first action.

DIVIDERS:

  • Use sparingly — prefer spacing to create visual separation.
  • In lists: SwiftUI List provides dividers automatically.
  • Custom dividers: Divider() with .padding(.horizontal) for inset style.

Dark Mode

DARK/LIGHT MODE:

  • 3-way picker (system/light/dark) is the standard pattern:

@AppStorage("appearance") private var appearance: String = "system" private var preferredColorScheme: ColorScheme? { switch appearance { case "light": return .light; case "dark": return .dark; default: return nil } } .preferredColorScheme(preferredColorScheme) // on outermost container in @main app

  • CRITICAL: .preferredColorScheme() MUST be in the root @main app, NOT just in the settings view.
  • System option: .preferredColorScheme(nil) follows device setting.
  • Settings screen: Picker with light/dark/system options writing to @AppStorage("appearance").

ADAPTIVE THEME COLORS (no color assets needed):

  • Switch ALL AppTheme palette colors from plain Color(hex:) to Color(light:dark:) with TWO hex values:

static let background = Color(light: Color(hex: "#F8F9FA"), dark: Color(hex: "#1C1C1E")) static let surface = Color(light: Color(hex: "#FFFFFF"), dark: Color(hex: "#2C2C2E"))

  • Color(light:dark:) uses UIColor(dynamicProvider:) — reacts to .preferredColorScheme() automatically.
  • YOU decide the dark palette based on app mood — user does not specify dark colors.
  • Dark palette guidelines: darken backgrounds (#1C1C1E, #2C2C2E), lighten/brighten accents slightly, use Color.primary/Color.secondary for text.
  • AppTheme MUST include the Color(light:dark:) extension (see shared constraints).

Design System Rules

AppTheme Pattern

Every app MUST use a centralized theme with nested enums for Colors, Fonts, and Spacing. Do NOT use a flat enum with top-level static properties.

// REQUIRED — always use nested enums
import SwiftUI

enum AppTheme {
    enum Colors {
        static let accent = Color.blue       // one accent per app
        static let textPrimary = Color.primary
        static let textSecondary = Color.secondary
        static let background = Color(.systemBackground)
        static let surface = Color(.secondarySystemBackground)
        static let cardBackground = Color(.secondarySystemGroupedBackground)
    }

    enum Fonts {
        static let largeTitle = Font.largeTitle
        static let title = Font.title
        static let headline = Font.headline
        static let body = Font.body
        static let caption = Font.caption
    }

    enum Spacing {
        static let small: CGFloat = 8
        static let medium: CGFloat = 16
        static let large: CGFloat = 24
        static let cornerRadius: CGFloat = 12
    }
}
// FORBIDDEN — never use flat structure
enum AppTheme {
    static let accentColor = Color.blue   // ❌ wrong
    static let spacing: CGFloat = 8       // ❌ wrong
}

Reference as: AppTheme.Colors.accent, AppTheme.Fonts.headline, AppTheme.Spacing.medium

Typography

  • System fonts only — use SwiftUI font styles: .largeTitle, .title, .headline, .body, .caption
  • No custom fonts, no downloaded fonts
  • Use AppTheme.Fonts for consistent sizing

Icons (SF Symbols)

  • SF Symbols only for all icons — required for every list row, button, empty state, and tab
  • Reference via Image(systemName: "symbol.name")
  • Pick domain-appropriate symbols (e.g. "checkmark.circle.fill" for todos, "note.text" for notes, "heart.fill" for favorites)
  • Use .symbolRenderingMode(.hierarchical) or .symbolRenderingMode(.palette) for visual depth
  • No custom icon assets unless the app concept specifically requires them

Colors

  • One accent color that fits the app's purpose
  • Use semantic colors: .primary, .secondary, Color(.systemBackground)
  • Do NOT add dark mode support, colorScheme checks, or custom dark/light color handling unless the user explicitly requests it

Spacing Standards

  • 16pt standard padding (outer margins, section spacing)
  • 8pt compact spacing (between related elements)
  • 24pt large spacing (between major sections)
  • Use AppTheme.Spacing constants throughout

Empty States

Every list or collection MUST have an empty state. Use ContentUnavailableView (iOS 17+) for a polished look:

// Required — show when collection is empty
if items.isEmpty {
    ContentUnavailableView(
        "No Notes Yet",
        systemImage: "note.text",
        description: Text("Tap + to create your first note")
    )
} else {
    // Show the list
}

For custom empty states, use a styled VStack with SF Symbol + descriptive text:

VStack(spacing: 16) {
    Image(systemName: "tray")
        .font(.system(size: 48))
        .foregroundStyle(.secondary)
    Text("Nothing here yet")
        .font(.title3)
    Text("Add your first item to get started")
        .font(.subheadline)
        .foregroundStyle(.secondary)
}

Animations

Use subtle, purposeful animations for state changes and list mutations:

// Toggle/complete actions — spring animation
withAnimation(.spring) {
    item.isComplete.toggle()
}

// List insertions/removals — combine opacity + scale
.transition(.opacity.combined(with: .scale))

// Numeric text changes
.contentTransition(.numericText())

// Filter/tab changes
.animation(.default, value: selectedFilter)

Rules:

  • Always use withAnimation(.spring) for toggle/complete state changes
  • Always add .transition(.opacity.combined(with: .scale)) for list add/remove
  • Never add gratuitous motion that slows down interaction
  • Keep animations subtle — .spring and .default curves only

Feedback States

LOADING PATTERNS:

  1. Inline button spinner (action on single element):
Button {
    Task { await save() }
} label: {
    if isSaving {
        ProgressView()
            .controlSize(.small)
    } else {
        Text("Save")
    }
}
.disabled(isSaving)
  1. Full-screen loading (initial data load):
if isLoading {
    ProgressView("Loading...")
} else {
    ContentView()
}
  1. Skeleton loading (content placeholders):
ForEach(Item.sampleData) { item in
    ItemRow(item: item)
}
.redacted(reason: .placeholder)
  1. Pull-to-refresh (list content):
List { ... }
    .refreshable { await viewModel.refresh() }
  1. Overlay loading (blocking operation):
.overlay {
    if isProcessing {
        ZStack {
            Color.black.opacity(0.3)
            ProgressView()
                .controlSize(.large)
                .tint(.white)
        }
        .ignoresSafeArea()
    }
}

LOADING RULES:

  • Show indicator for operations > 300ms.
  • ALWAYS disable the triggering button while loading (prevents double-taps).
  • Never block the entire UI for a partial operation — use inline spinner.
  • Match loading style to scope: button-level → inline, screen-level → full-screen.

ERROR HANDLING UI:

  1. Inline validation (below form fields):
if let error = emailError {
    HStack(spacing: 4) {
        Image(systemName: "exclamationmark.circle.fill")
        Text(error)
    }
    .font(.caption)

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [Abdullah4AI](https://github.com/Abdullah4AI)
- **Source:** [Abdullah4AI/apple-developer-toolkit](https://github.com/Abdullah4AI/apple-developer-toolkit)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.