
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.
- Installs
- 179
- Last 24h
- —
- Trending rank
- —
- Security
- Not audited
Install
Works with every agent the skills CLI supports.
npx skills add https://github.com/travisjneuman/.claude --skill ios-development- 01
Install
Run the command in your project. The CLI asks which agents to install for.
- 02
Describe the task
Ask your agent for something this skill covers. It loads ios-development automatically when relevant.
- 03
Review the result
The agent follows the SKILL.md steps, scripts and references bundled with the skill.
SKILL.md
The instructions your agent reads when the skill is triggered.
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
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
// 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
// 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+)
// 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
// 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
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
// 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
// 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+)
@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
// 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
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
.safeAreaInset(edge: .bottom) {
BottomBar()
}
// Ignore safe area
.ignoresSafeArea(.keyboard)
.ignoresSafeArea(.container, edges: .bottom)
Platform-Specific
iPadOS Specifics
// Split view
NavigationSplitView {
Sidebar()
} content: {
ContentList()
} detail: {
DetailView()
}
// Keyboard shortcuts
.keyboardShortcut("n", modifiers: .command)
// Hover effects
.hoverEffect(.highlight)
tvOS Specifics
// 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
<!-- 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
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<false/>
</dict>
Testing
Unit Tests
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
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)
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
// 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
// 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
// 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
// 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
}
}
FAQ
What does the ios-development 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.
How do I install ios-development?
Run npx skills add https://github.com/travisjneuman/.claude --skill ios-development in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/ios-development/ (personal) or .claude/skills/ios-development/ (project).
Which AI agents can use ios-development?
Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.
Is ios-development safe to install?
No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.
Who maintains ios-development?
It is published by travisjneuman in travisjneuman/.claude. It has 179 recorded installs.
More from travisjneuman/.claude
document-skills
travisjneuman/.claude
Professional document creation, editing, and analysis for Office formats (docx, pdf, pptx, xlsx). Use when working with Word documents, PDFs, PowerPoint presentations, or Excel spreadsheets.
brand-identity
travisjneuman/.claude
Brand strategy and identity design for businesses and products. Use when creating brand guidelines, developing visual identity systems, or defining brand positioning.
travel-planner
travisjneuman/.claude
Travel destination research and daily itinerary creation with logistics planning, budget tracking, and experience optimization. Use when planning trips, creating travel itineraries, comparing destinations, or organizing travel logistics.
business-strategy
travisjneuman/.claude
Business strategy expertise for strategic planning, competitive analysis, market entry, M&A strategy, portfolio management, and strategic decision-making. Use when analyzing competitive positioning, planning growth strategies, or making strategic decisions.
graphic-design
travisjneuman/.claude
Professional graphic design principles for digital and print media. Use when creating visual designs, choosing color palettes, typography, layouts, or providing design feedback.
real-estate-analyzer
travisjneuman/.claude
Open the skill to read its SKILL.md instructions.
Popular mobile skills
design-mobile-apps
designed-by-ai/skills
Design mobile app screens with Sleek, edit Sleek projects, and implement their designs in React Native or HTML.
ios-design
uizze.sh
Design and review iOS UI/UX for iPhone apps in SwiftUI, UIKit, or React Native. Use for mobile app design, screen redesigns, navigation flows, typography, forms, keyboard and safe-area layout, and accessibility checks. Apply Apple Human Interface Guidelines and consult real product references through the optional Uizze connection.
vercel-react-native-skills
vercel-labs/agent-skills
React Native and Expo best practices for building performant mobile apps. Use when building React Native components, optimizing list performance, implementing animations, or working with native modules. Triggers on tasks involving React Native, Expo, mobile performance, or native platform APIs.
animate-expo
emilkowalski/skills
Build animations in React Native and Expo, making the decisions in the order that determines whether they feel right — should it animate, which thread it runs on, which properties, spring or timing, how the gesture hands off, how it degrades. Writes the implementation with Reanimated, Gesture Handler, Expo Router and expo-haptics. Use when animating anything in an Expo app, adding gestures, sheets, screen transitions, press feedback or haptics, or fixing motion that stutters on device. For web animation use animate.
xcode-project-setup
firebase/agent-skills
Safely modifies Xcode projects (.pbxproj) to add Swift Packages and link files. Use this skill whenever an iOS project needs dependencies installed (e.g. Firebase, Alamofire).
firebase-remote-config-basics
firebase/agent-skills
Manages Firebase Remote Config templates, feature flags, loading strategies, and SDKs (Android, iOS). Use when downloading/deploying remoteconfig JSON templates, managing version history/feature flags, setting in-app defaults, fetchAndActivate(), real-time listeners, or SDK setup. Don't use for Firebase Hosting, Auth, Firestore, Data Connect, Crashlytics, or App Hosting.