Skill v1.0.0
currentAutomated scan100/100version: "1.0.0" name: rcode-ios description: iOS-specific adaptations of the R.Code atomic development workflow (toolchain defaults, SwiftData patterns, Swift Testing, mandatory i18n gate, /phase-gate overrides). Use when working in a R.Code-managed iOS project — .rcode/ exists alongside .xcodeproj or Package.swift. Triggers on "iOS", "SwiftData", "SwiftUI", "Swift Testing", "SwiftLint", ".xcodeproj", "Package.swift", "Xcode", "@Observable", "Localizable.xcstrings", "iOS app bauen", "iOS projekt", "iOS entwickeln", "swift tests schreiben".
R.Code-iOS
iOS-specific adaptations of the R.Code atomic development workflow. Derived from IMP-008 (5 conversation logs, 87MB) and 2026-04-17 toolchain decisions on a reference iOS project. Use when.rcode/exists and the project is iOS (.xcodeprojorPackage.swift). On-demand skill — demoted from always-loaded rule per IMP-079 (2026-07-03).
Toolchain Defaults (2026-04)
| Tool | Default | Alternative | Rationale | |
|---|---|---|---|---|
| Xcode project | Raw `.xcodeproj` | XcodeGen, Tuist | Fewer moving parts. Apple tooling is first-class citizen. Team scale ≤ 3 devs. | |
| Testing framework | Swift Testing | XCTest | Swift Testing is the 2026 standard; XCTest only for legacy targets or UI tests not yet ported. | |
| Lint | SwiftLint | swift-format | SwiftLint has strict mode + broad rule coverage. Swift-format is formatting only. | |
| UI framework | SwiftUI + @Observable | UIKit, Combine | @Observable (iOS 17+) replaces ObservableObject with less boilerplate. | |
| Persistence | SwiftData | Core Data, Realm | SwiftData is native, less boilerplate than Core Data. Core Data only for legacy. | |
| Dependency Manager | SPM | CocoaPods | Apple's direction. CocoaPods only for libraries not yet on SPM. |
Default these unless the project has explicit reason to differ.
Issue Workflow (iOS-Specific)
Step Sequence (compressed vs. web: 5 vs. 10 steps)
iOS work compresses ~/.claude/commands/issue.md's 10 generic Steps (0–9) into 5 iOS-specific steps — this is a different, iOS-only step count, not a redefinition of the web Step sequence:
- Explore & Understand — Read the unit, map affected views/models/services. Check relevant SwiftData schema.
- Test-First — Write failing Swift Testing tests that describe the desired behavior.
- Implement — Minimum code to pass tests. Respect separation: View (SwiftUI) / ViewModel (@Observable) / Model (SwiftData).
- i18n Gate — All user-facing strings in
Localizable.xcstrings. No hardcoded strings in views. This step is mandatory; blocks/phase-gate. - Review & Ship — Run the project's check trio (CLAUDE.md → Mandatory Pre-Commit — for this stack, typically SwiftLint strict, Swift Testing, Xcode build), plus confirm the preview renders without crash.
Mandatory i18n Gate
Before any PR merges:
# Check for hardcoded string literals in SwiftUI viewsgrep -rn '"[A-Z][a-z].*"' --include="*.swift" Views/ | grep -v "Localizable" | grep -v "LocalizedStringKey"
Any hit in a view must be moved to Localizable.xcstrings with a translation key.
Why mandatory: Retrofitting i18n is 10x the cost of in-line discipline. Field evidence: 2 sprints lost to retrofit on an earlier iOS project.
SwiftData Patterns
@Model for persistent, @Observable for ephemeral
@Model final class User { // persisted to SwiftData storevar id: UUIDvar name: String}@Observable final class ProfileViewModel { // view-scoped statevar user: Uservar isEditing = false}
Query in the View, not the ViewModel
struct UserList: View {@Query(sort: \User.name) var users: [User] // ✓ SwiftData handles invalidationvar body: some View { ... }}
Migrations require VersionedSchema
enum UserSchemaV1: VersionedSchema {static var versionIdentifier = Schema.Version(1, 0, 0)static var models: [any PersistentModel.Type] { [User.self] }}
Never modify a @Model class without a migration plan.
Testing Patterns (Swift Testing)
import Testing@Suite("SplittingCalculator")struct SplittingCalculatorTests {@Test("calculates 50-50 split correctly")func fiftyFifty() async throws {let result = SplittingCalculator.split(100_000, ratio: 0.5)#expect(result == (50_000, 50_000))}@Test(arguments: [100_000, 200_000, 50_000])func handlesVariousAmounts(_ amount: Int) async throws {let result = SplittingCalculator.split(amount, ratio: 0.5)#expect(result.0 + result.1 == amount)}}
No XCTestCase subclassing in new code. Parametric tests via arguments:. Async tests are first-class.
SwiftLint Configuration
Required .swiftlint.yml:
opt_in_rules:- empty_count- closure_spacing- contains_over_first_not_nil- force_unwrappingdisabled_rules:- line_length # too noisy for SwiftUIstrict: true
Enforce force_unwrapping — ! is a red flag outside test fixtures.
Anti-Patterns
❌ ObservableObject for new code
@Observable supersedes ObservableObject. Only use ObservableObject when integrating with UIKit or legacy.
❌ Core Data for new projects
SwiftData is the 2026 default. Only choose Core Data if you need features SwiftData lacks (CloudKit custom merges, NSFetchedResultsController).
❌ Hardcoded strings in views
Blocks i18n gate. No merge.
❌ XcodeGen/Tuist without team scale justification
Adds a toolchain hop for solo/small-team projects. Only valuable at 5+ devs or multi-target complexity.
❌ XCTest for new test files
Swift Testing is the default. XCTest only for UI tests not yet ported.
Phase-Gate Command Override
/phase-gate <N> verifies Phase N (project milestone) is complete before unlocking the next. In iOS projects it additionally verifies:
- The project's check trio (CLAUDE.md → Mandatory Pre-Commit) — for this stack, typically: SwiftLint strict mode passes with 0 warnings, Swift Testing run exits 0 with all tests passing,
xcodebuild -scheme <scheme> buildsucceeds Localizable.xcstringscontains entries for all user-facing strings- No
Math.random()or equivalent in crypto-relevant code paths
Example Reference Case
.rcode/inside the app for framework development (see theframework-extractionskill)- Decision 2026-04-17: Raw Xcode + Swift Testing + SwiftLint — no XcodeGen/Tuist
- MVP scope: Bootstrap, iOS-specific rules, Project docs, /issue + /phase-gate command overrides
- Reference implementation for R.Code-iOS extraction (see IMP-012)
References
- IMP-008 (iOS Workflow v2, 2026-02-22) in improvement-ledger.json
- Reference iOS project at
~/code/your-ios-app - Swift Testing: https://developer.apple.com/documentation/testing
- SwiftData migrations: https://developer.apple.com/documentation/swiftdata/schema-migrations