| name | iOS Development for Prop Pigeon |
| description | Swift and iOS best practices for this iMessage game project |
| when_to_use | Working with Swift files, iOS frameworks, or Xcode projects |
| version | 1.0.0 |
| languages | swift |
iOS Development Guidelines
Context
You're building Prop Pigeon - an iMessage game extension with strict constraints and requirements.
Critical Rules
Swift Style (MANDATORY)
- Swift 6 with strict concurrency (@MainActor, Sendable)
- NO force unwrapping (!) - Use guard/if let or optional chaining
- async/await over completion handlers
- Value types first - Prefer structs over classes
- Protocol-oriented - Define protocols for flexibility
- Exhaustive switches - No default for enums
Memory Management (CRITICAL)
iMessage Extension Limit: 120MB HARD
- Profile with Instruments regularly
- Use texture atlases for game sprites
- Release resources when games end
- Weak delegates, unowned captures
- Check for retain cycles
- Memory per game target: <10MB
Architecture (REQUIRED)
MVVM Pattern:
View (SwiftUI) → ViewModel (@ObservableObject) → Model (Struct)
Example:
PongView.swift → PongViewModel.swift → PongGame.swift
- SwiftUI only for all UI
- Combine for reactive streams
- Core Data for persistence
- No singletons except CurrencyManager
Testing (MANDATORY - TDD)
Superpowers enforces TDD. Follow it.
- Write test FIRST
80% coverage for game logic
- 100% coverage for currency
- XCTest for unit tests
- XCUITest for UI flows
- Mock Messages framework
File Organization
PropPigeon/
├── App/
│ ├── PropPigeonApp.swift
│ └── AppDelegate.swift
├── Models/
│ ├── Game.swift (protocols)
│ ├── Player.swift
│ └── Currency.swift
├── Services/
│ ├── CurrencyManager.swift
│ └── GameEngine.swift
├── Views/
│ └── Shared/
└── Utilities/
PropPigeonMessagesExtension/
├── MessagesViewController.swift
├── Games/
│ ├── EightBall/
│ ├── Blackjack/
│ └── War/
└── UI/
Common Patterns
SwiftUI View Structure
struct GameView: View {
@StateObject var viewModel: GameViewModel
var body: some View {
ZStack {
GameScene(viewModel: viewModel)
ControlsOverlay(viewModel: viewModel)
}
.task {
await viewModel.initialize()
}
}
}
ViewModel Pattern
@MainActor
class GameViewModel: ObservableObject {
@Published var gameState: GameState
private let messageHandler: MessageHandler
init(messageHandler: MessageHandler) {
self.messageHandler = messageHandler
self.gameState = .initial
}
func makeMove(_ move: GameMove) async {
guard validateMove(move) else { return }
try? await applyMove(move)
if let result = checkWinCondition() {
await handleGameEnd(result)
} else {
await sendGameState()
}
}
}
Error Handling
enum GameError: Error {
case invalidMove
case notPlayerTurn
case insufficientFunds
case messageFailed
case stateCorrupted
}
// Usage
do {
try game.applyMove(move)
} catch GameError.invalidMove {
showError("Invalid move")
} catch {
showError("Unexpected error: \(error)")
}
Performance Targets
- Startup: <2 seconds
- Frame rate: 60fps minimum
- Memory: <120MB peak (extension)
- State serialization: <100ms
When This Skill Activates
- Creating/editing .swift files
- Building Xcode projects
- Working with SwiftUI
- Implementing iOS-specific features
- Debugging iOS issues
References
- Swift Style Guide: Apple's Swift API Design Guidelines
- Concurrency: Swift 6 strict concurrency docs
- Memory: iOS Memory Management Guide