travisjneuman/.claude/ios-development

Claude Code and agent skill by travisjneuman/.claude

Trust tier 2
  • community
LLM review pending
Source check pending

Checking that the SKILL.md file is still in the repository.

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.

Install

npx vskill@latest install travisjneuman/.claude/ios-development

SKILL.md

iOS/iPadOS/tvOS Development

Comprehensive guide for building native Apple platform applications.

Platforms Covered

PlatformMinimum TargetCurrent
iOSiOS 15.0+iOS 17+
iPadOSiPadOS 15.0+iPadOS 17+
tvOStvOS 15.0+tvOS 17+
watchOSwatchOS 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
ElementSize
Touch target44x44 pt minimum
Navigation bar44 pt
Tab bar49 pt
Status bar47 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 varvar (automatic)
@StateObject@State
@ObservedObjectDirect 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
}

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewNot runA model review is scheduled for the next rescan.

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
69
Repository forks
17
7-day trend score
21

Stars and forks are for the whole GitHub repository, not this skill alone. Refreshed 24 Mar 2026, 7 months ago.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/travisjneuman/.claude/ios-development/badge)](https://verified-skill.com/skills/travisjneuman/.claude/ios-development)

Something wrong with this skill or its page? Tell us.