# IOS Std

> Coding standards for iOS apps (SwiftUI, MVVM, offline-first, push notifications). Use when this capability is needed.

- Skill: `tomevault-io/ios-std` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add tomevault-io/ios-std`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tomevault-io/ios-std/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: tomevault-io (https://skillmd.com/u/tomevault-io)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tomevault-io/ios-std

---


## Purpose

Ensure consistent, testable, app store-ready iOS code. Complements stack, security, and NFR skills.

## Project structure

```
Features/<FeatureName>/
  View.swift
  ViewModel.swift
  (feature-specific helpers)
UIComponents/              # Shared UI primitives
Services/
  Persistence/             # SwiftData/CoreData boundary
  Notifications/           # Push notification handling
  Logging/                 # os.Logger wrapper
  Config/                  # Environment/config
Infrastructure/            # Routing, composition root, cross-cutting utilities
```

## Key conventions

### SwiftUI

- Views are small and composable; extract subviews when files grow
- State ownership: `@StateObject` for owned, `@ObservedObject` for injected
- No side effects in `body`; use `task`, `onAppear`, or explicit actions

### MVVM

- View models: `@MainActor`, expose state via `@Published`, intents as methods (`load()`, `didTapSave()`)
- Services injected via initializers
- View models never touch SwiftData/CoreData directly—use persistence boundary

### Persistence (offline-first)

- Single boundary: `PersistenceStore` protocol with `SwiftDataStore` implementation
- Schema changes require migration note
- User-generated content needs retention/cleanup strategy

### Notifications

- Isolated handling: permission flow, token registration, payload routing
- Treat all payload values as untrusted input

### Concurrency

- Mark view models `@MainActor` when they own UI state
- Handle cancellation explicitly in long-running operations

### Logging

- Use `os.Logger`
- Log events, not payloads; never log PII or tokens

### Naming

- Types/files: `PascalCase`
- Functions/vars: `camelCase`
- One primary type per file
- Feature folders: `PascalCase`

## Testing requirements

**Framework:** Swift Testing (`import Testing`, `@Test`, `#expect`). Do not use XCTest for unit tests.

TDD is mandatory — see `/shared-tdd` for the red-green-refactor workflow and evidence requirements.

**Test command:** `xcodebuild test` or run via Xcode.

### What to test

- Unit tests: view model logic, persistence store (in-memory double), notification routing
- UI tests: primary happy path, notification-to-screen flow when relevant
- Use `@Test(arguments:)` for parameterized tests instead of loops

### Test file conventions

| Source | Test |
|--------|------|
| `Features/Foo/FooViewModel.swift` | `Tests/Features/Foo/FooViewModelTests.swift` |
| `Services/Bar/BarService.swift` | `Tests/Services/Bar/BarServiceTests.swift` |

Test pattern: `@Test func methodName_condition_expected()` (no `test_` prefix)

## Output

For significant code changes, briefly note:

- Any deviation from these standards and why

## Reference

For detailed patterns and examples, see `reference/ios-std-reference.md`

---
> Converted and distributed by [TomeVault](https://tomevault.io/claim/edfenton) — claim your Tome and manage your conversions.
<!-- tomevault:4.0:skill_md:2026-04-13 -->

