ios-development
iOS, iPadOS, and tvOS development with Swift, SwiftUI, and UIKit. Use when building Apple platform apps, implementing iOS-specific features, or following Apple Human Interface Guidelines.
Best use case
ios-development is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
iOS, iPadOS, and tvOS development with Swift, SwiftUI, and UIKit. Use when building Apple platform apps, implementing iOS-specific features, or following Apple Human Interface Guidelines.
Teams using ios-development should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/ios-development/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How ios-development Compares
| Feature / Agent | ios-development | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
iOS, iPadOS, and tvOS development with Swift, SwiftUI, and UIKit. Use when building Apple platform apps, implementing iOS-specific features, or following Apple Human Interface Guidelines.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# iOS/iPadOS/tvOS Development
Comprehensive guide for building native Apple platform applications.
## Platforms Covered
| Platform | Minimum Target | Current |
| -------- | -------------- | ----------- |
| iOS | iOS 15.0+ | iOS 17+ |
| iPadOS | iPadOS 15.0+ | iPadOS 17+ |
| tvOS | tvOS 15.0+ | tvOS 17+ |
| watchOS | watchOS 8.0+ | watchOS 10+ |
---
## SwiftUI (Preferred for New Projects)
### Basic Structure
```swift
import SwiftUI
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
@State private var count = 0
var body: some View {
VStack(spacing: 20) {
Text("Count: \(count)")
.font(.largeTitle)
Button("Increment") {
count += 1
}
.buttonStyle(.borderedProminent)
}
.padding()
}
}
```
### State Management
```swift
// Local state
@State private var text = ""
// Binding (child components)
@Binding var isPresented: Bool
// Observable object
@StateObject private var viewModel = MyViewModel()
@ObservedObject var viewModel: MyViewModel
// Environment
@Environment(\.dismiss) private var dismiss
@Environment(\.colorScheme) private var colorScheme
@EnvironmentObject var appState: AppState
// App Storage (UserDefaults)
@AppStorage("username") private var username = ""
```
### Modern Concurrency
```swift
// Async/await
func fetchData() async throws -> [Item] {
let url = URL(string: "https://api.example.com/items")!
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode([Item].self, from: data)
}
// In View
.task {
do {
items = try await fetchData()
} catch {
errorMessage = error.localizedDescription
}
}
// Main actor for UI updates
@MainActor
class ViewModel: ObservableObject {
@Published var items: [Item] = []
func load() async {
items = try? await fetchData()
}
}
```
### Navigation (iOS 16+)
```swift
// NavigationStack with typed destinations
struct ContentView: View {
@State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
List(items) { item in
NavigationLink(value: item) {
Text(item.name)
}
}
.navigationDestination(for: Item.self) { item in
DetailView(item: item)
}
}
}
}
```
### Lists and Grids
```swift
// Modern List
List {
ForEach(items) { item in
ItemRow(item: item)
}
.onDelete(perform: delete)
.onMove(perform: move)
}
.listStyle(.insetGrouped)
.searchable(text: $searchText)
.refreshable {
await refresh()
}
// LazyVGrid
LazyVGrid(columns: [
GridItem(.adaptive(minimum: 150))
]) {
ForEach(items) { item in
ItemCard(item: item)
}
}
```
---
## UIKit (Legacy/Complex UI)
### View Controller Lifecycle
```swift
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// About to appear
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// Fully visible
}
}
```
### Auto Layout
```swift
// Programmatic constraints
NSLayoutConstraint.activate([
label.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
label.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
label.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16)
])
```
---
## Data Persistence
### Core Data
```swift
// Model
@objc(Item)
public class Item: NSManagedObject {
@NSManaged public var id: UUID
@NSManaged public var name: String
@NSManaged public var createdAt: Date
}
// Fetch
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Item.createdAt, ascending: false)],
animation: .default
)
private var items: FetchedResults<Item>
```
### SwiftData (iOS 17+)
```swift
@Model
class Item {
var id: UUID
var name: String
var createdAt: Date
init(name: String) {
self.id = UUID()
self.name = name
self.createdAt = Date()
}
}
// In View
@Query(sort: \Item.createdAt, order: .reverse)
private var items: [Item]
@Environment(\.modelContext) private var modelContext
func addItem() {
let item = Item(name: "New Item")
modelContext.insert(item)
}
```
### Keychain
```swift
// Store sensitive data
import Security
func saveToKeychain(key: String, data: Data) throws {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key,
kSecValueData as String: data
]
SecItemDelete(query as CFDictionary)
let status = SecItemAdd(query as CFDictionary, nil)
guard status == errSecSuccess else {
throw KeychainError.saveFailed
}
}
```
---
## Networking
### URLSession
```swift
actor NetworkService {
func fetch<T: Decodable>(_ type: T.Type, from url: URL) async throws -> T {
let (data, response) = try await URLSession.shared.data(from: url)
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
throw NetworkError.invalidResponse
}
return try JSONDecoder().decode(T.self, from: data)
}
}
```
---
## Apple Human Interface Guidelines
### iOS Design Principles
- **Clarity**: Text is legible, icons precise, adornments subtle
- **Deference**: Content is paramount, UI doesn't compete
- **Depth**: Visual layers and realistic motion convey hierarchy
### Key Metrics
| Element | Size |
| -------------- | ------------------------------ |
| Touch target | 44x44 pt minimum |
| Navigation bar | 44 pt |
| Tab bar | 49 pt |
| Status bar | 47 pt (notch) / 20 pt (legacy) |
### Safe Areas
```swift
.safeAreaInset(edge: .bottom) {
BottomBar()
}
// Ignore safe area
.ignoresSafeArea(.keyboard)
.ignoresSafeArea(.container, edges: .bottom)
```
---
## Platform-Specific
### iPadOS Specifics
```swift
// Split view
NavigationSplitView {
Sidebar()
} content: {
ContentList()
} detail: {
DetailView()
}
// Keyboard shortcuts
.keyboardShortcut("n", modifiers: .command)
// Hover effects
.hoverEffect(.highlight)
```
### tvOS Specifics
```swift
// Focus management
@FocusState private var isFocused: Bool
Button("Action") { }
.focused($isFocused)
.focusable()
// Large text for 10-foot UI
.font(.system(size: 38))
```
---
## App Store Requirements
### Required Assets
- App icon (1024x1024)
- Screenshots for each device size
- Privacy policy URL
- App description (4000 char max)
### Privacy
```xml
<!-- Info.plist -->
<key>NSCameraUsageDescription</key>
<string>We need camera access to...</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>We need photo access to...</string>
```
### App Transport Security
```xml
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<false/>
</dict>
```
---
## Testing
### Unit Tests
```swift
import XCTest
@testable import MyApp
final class MyTests: XCTestCase {
func testExample() async throws {
let sut = MyViewModel()
await sut.load()
XCTAssertEqual(sut.items.count, 10)
}
}
```
### UI Tests
```swift
import XCTest
final class MyUITests: XCTestCase {
let app = XCUIApplication()
override func setUp() {
continueAfterFailure = false
app.launch()
}
func testNavigation() {
app.buttons["Start"].tap()
XCTAssertTrue(app.staticTexts["Welcome"].exists)
}
}
```
---
## Project Structure
```
MyApp/
├── App/
│ └── MyApp.swift
├── Features/
│ ├── Home/
│ │ ├── HomeView.swift
│ │ └── HomeViewModel.swift
│ └── Settings/
│ └── SettingsView.swift
├── Core/
│ ├── Models/
│ ├── Services/
│ └── Extensions/
├── Resources/
│ ├── Assets.xcassets
│ └── Localizable.strings
└── Tests/
```
---
## Best Practices
### DO:
- Use SwiftUI for new projects
- Support Dynamic Type for accessibility
- Handle all device orientations
- Implement proper error handling
- Use Swift's type system fully
### DON'T:
- Force unwrap optionals without validation
- Block main thread with network calls
- Hardcode strings (use localization)
- Ignore memory management (retain cycles)
- Skip accessibility labels
---
## @Observable Macro (iOS 17+, Replacing ObservableObject)
```swift
import Observation
// New pattern: @Observable macro (replaces ObservableObject + @Published)
@Observable
class UserViewModel {
var user: User?
var isLoading = false
var errorMessage: String?
func load() async {
isLoading = true
defer { isLoading = false }
do {
user = try await fetchUser()
} catch {
errorMessage = error.localizedDescription
}
}
}
// In View: no @StateObject/@ObservedObject wrappers needed
struct UserView: View {
@State private var viewModel = UserViewModel()
var body: some View {
Group {
if viewModel.isLoading {
ProgressView()
} else if let user = viewModel.user {
Text(user.name)
}
}
.task { await viewModel.load() }
}
}
// @Bindable for two-way bindings
struct EditView: View {
@Bindable var viewModel: EditViewModel
var body: some View {
TextField("Name", text: $viewModel.name)
}
}
```
### Migration from ObservableObject
| Old (ObservableObject) | New (@Observable) |
| -------------------------- | ------------------------- |
| `class: ObservableObject` | `@Observable class` |
| `@Published var` | `var` (automatic) |
| `@StateObject` | `@State` |
| `@ObservedObject` | Direct reference |
| `@EnvironmentObject` | `@Environment(TypeName.self)` |
---
## SwiftUI 6 New Views and Modifiers
```swift
// Mesh gradient (iOS 18+)
MeshGradient(
width: 3, height: 3,
points: [
.init(0, 0), .init(0.5, 0), .init(1, 0),
.init(0, 0.5), .init(0.5, 0.5), .init(1, 0.5),
.init(0, 1), .init(0.5, 1), .init(1, 1)
],
colors: [
.red, .purple, .indigo,
.orange, .cyan, .blue,
.yellow, .green, .mint
]
)
// Zoom navigation transition
NavigationLink {
DetailView(item: item)
} label: {
ItemCard(item: item)
}
.matchedTransitionSource(id: item.id, in: namespace)
// ScrollView enhancements
ScrollView {
LazyVStack {
ForEach(items) { item in
ItemRow(item: item)
}
}
}
.scrollPosition(id: $scrolledID)
.defaultScrollAnchor(.bottom)
// Custom containers
@Entry macro for EnvironmentValues
@Environment(\.myCustomValue) var value
```
---
## Swift 6 Concurrency
```swift
// Complete data race safety (strict concurrency checking)
// Enable in Xcode: SWIFT_STRICT_CONCURRENCY = complete
// Sendable conformance required for data crossing isolation boundaries
struct UserData: Sendable {
let id: UUID
let name: String
}
// Actor isolation
actor DatabaseManager {
private var cache: [String: Data] = [:]
func get(_ key: String) -> Data? { cache[key] }
func set(_ key: String, value: Data) { cache[key] = value }
}
// Global actors
@MainActor
class ViewModel {
var items: [Item] = []
func load() async {
let data = await fetchFromNetwork() // Runs off main actor
items = data // Back on main actor automatically
}
}
// Typed throws (Swift 6)
enum DataError: Error {
case notFound, corrupted
}
func loadData() throws(DataError) -> Data {
guard let data = cache.get("key") else {
throw .notFound
}
return data
}
```
---
## Minimum Target: iOS 17+
For new projects, target iOS 17+ to access:
| Feature | Minimum iOS | Benefit |
| --------------- | ----------- | -------------------------------- |
| @Observable | 17 | Simpler state management |
| SwiftData | 17 | Modern persistence (replaces Core Data) |
| NavigationStack | 16 | Type-safe navigation |
| Swift Charts | 16 | Native charting |
| MeshGradient | 18 | Beautiful gradients |
| ControlWidget | 18 | Control Center widgets |
---
## visionOS and Spatial Computing
```swift
// visionOS app structure
@main
struct MyVisionApp: App {
var body: some Scene {
// 2D window
WindowGroup {
ContentView()
}
// Immersive space
ImmersiveSpace(id: "immersiveScene") {
ImmersiveView()
}
// 3D volume
WindowGroup(id: "3dContent") {
Model3D(named: "Globe") { model in
model.resizable()
.aspectRatio(contentMode: .fit)
} placeholder: {
ProgressView()
}
}
.windowStyle(.volumetric)
}
}
// RealityKit integration
import RealityKit
struct ImmersiveView: View {
var body: some View {
RealityView { content in
let sphere = MeshResource.generateSphere(radius: 0.5)
let material = SimpleMaterial(color: .blue, isMetallic: true)
let entity = ModelEntity(mesh: sphere, materials: [material])
content.add(entity)
}
.gesture(TapGesture().targetedToAnyEntity().onEnded { value in
// Handle spatial tap
})
}
}
```
---
## watchOS Development
```swift
// watchOS app with SwiftUI
@main
struct MyWatchApp: App {
var body: some Scene {
WindowGroup {
NavigationStack {
ContentView()
}
}
}
}
// Complications / Widgets
struct MyWidget: Widget {
var body: some WidgetConfiguration {
StaticConfiguration(kind: "myWidget", provider: Provider()) { entry in
MyWidgetView(entry: entry)
}
.configurationDisplayName("My Widget")
.supportedFamilies([
.accessoryCircular,
.accessoryRectangular,
.accessoryInline,
])
}
}
// Watch Connectivity for iPhone <-> Watch communication
import WatchConnectivity
class ConnectivityManager: NSObject, WCSessionDelegate {
func session(_ session: WCSession, didReceiveMessage message: [String: Any]) {
// Handle message from paired device
}
}
```Related Skills
svelte-development
Svelte 5 development with runes ($state, $derived, $effect), SvelteKit full-stack framework, and modern reactive patterns. Use when building Svelte applications, implementing fine-grained reactivity, or working with SvelteKit routing and server functions.
serverless-development
AWS Lambda, Vercel Edge Functions, Cloudflare Workers, cold starts, deployment patterns, and infrastructure as code (SST, Serverless Framework). Use when building serverless applications or optimizing function-based architectures.
pwa-development
Progressive Web App development for installable, offline-capable web applications. Use when building PWAs, implementing service workers, or creating offline-first experiences.
llm-app-development
LLM app development with RAG, prompt engineering, vector databases, and AI agents
game-development
Game development with Unity, Unreal Engine, and Godot. Use when building games, implementing game mechanics, physics, AI, or working with game engines.
flutter-development
Cross-platform development with Flutter and Dart for iOS, Android, Web, Desktop, and embedded. Use when building Flutter apps, implementing Material/Cupertino design, or optimizing Dart code.
android-development
Android development with Kotlin, Jetpack Compose, and modern Android architecture. Use when building Android apps, implementing Material Design, or following Android best practices.
example-skill
Example skill - replace with your skill's description and activation keywords
websockets-realtime
Real-time communication with WebSockets, Server-Sent Events, and related technologies. Use when building chat, live updates, collaborative features, or any real-time functionality.
video-production
Professional video production from planning to delivery. Use when creating video content, editing workflows, motion graphics, or optimizing video for different platforms.
ui-research
Research-first UI/UX design workflow. Use BEFORE any frontend visual work to research modern patterns, gather inspiration from real products, and avoid generic AI-generated looks. Mandatory prerequisite for quality UI work.
ui-animation
Motion design and animation for user interfaces. Use when creating micro-interactions, page transitions, loading states, or any UI animation across web and mobile platforms.