ios-design
SKILL.md
iOS Design Skill
Expert knowledge of Apple Human Interface Guidelines for SwiftUI applications. This skill enforces design excellence, accessibility compliance, and iOS-native feel.
Core Principles
1. iOS Native Feel
- Use system colors and semantic tokens
- Follow Dynamic Type for text scaling
- Respect Safe Areas and notches
- Support both light and dark mode
2. Accessibility First
- Minimum 44x44pt touch targets
- 4.5:1 contrast ratio for normal text
- Proper
accessibilityLabelon all interactive elements - Support Dynamic Type up to AX5 (largest accessibility sizes)
3. SwiftUI Best Practices
- Use correct property wrappers (@State, @StateObject, @ObservedObject)
- Prefer declarative patterns
- Avoid AnyView type erasure
- Provide stable IDs in ForEach
Typography Scale (11 Styles)
| Style | Size (Default) | Weight | Use Case |
|---|---|---|---|
.largeTitle |
34pt | Regular | Hero headlines |
.title |
28pt | Regular | Page titles |
.title2 |
22pt | Regular | Section headers |
.title3 |
20pt | Regular | Subsection headers |
.headline |
17pt | Semibold | Emphasized body |
.body |
17pt | Regular | Main content |
.callout |
16pt | Regular | Secondary content |
.subheadline |
15pt | Regular | Supporting text |
.footnote |
13pt | Regular | Metadata |
.caption |
12pt | Regular | Captions |
.caption2 |
11pt | Regular | Small labels |
Usage Pattern
// GOOD - System text styles
Text("Welcome")
.font(.largeTitle)
// BAD - Hardcoded sizes
Text("Welcome")
.font(.system(size: 34))
Dynamic Type Support
// Automatic scaling with system styles
Text("Body text")
.font(.body)
// Custom sizes that scale
@ScaledMetric(relativeTo: .body) var iconSize: CGFloat = 24
Image(systemName: "star.fill")
.frame(width: iconSize, height: iconSize)
Color System
Semantic Colors
| Token | Light | Dark | Use Case |
|---|---|---|---|
.label |
Black | White | Primary text |
.secondaryLabel |
Gray | Light gray | Secondary text |
.tertiaryLabel |
Light gray | Darker gray | Tertiary text |
.quaternaryLabel |
Lighter gray | Darkest gray | Disabled text |
.systemBackground |
White | Black | Primary background |
.secondarySystemBackground |
Light gray | Dark gray | Grouped content |
.tertiarySystemBackground |
White | Darker gray | Elevated content |
.separator |
Light gray | Dark gray | Dividers |
.link |
System blue | System blue | Interactive links |
System Colors
| Color | Use Case |
|---|---|
.accentColor |
Primary interactive elements |
.red |
Destructive actions, errors |
.orange |
Warnings |
.yellow |
Caution |
.green |
Success, positive |
.blue |
Default interactive |
.purple |
Special features |
.pink |
Playful accents |
Usage Pattern
// GOOD - Semantic colors
Text("Primary content")
.foregroundColor(.label)
VStack {
// content
}
.background(Color(.systemBackground))
// BAD - Hardcoded colors
Text("Primary content")
.foregroundColor(.black)
VStack {
// content
}
.background(Color.white)
Touch Targets
Requirements
- Minimum: 44x44pt (Apple HIG requirement)
- Bottom navigation: 46-50pt recommended
- Spacing: 8pt minimum between targets
Implementation
// GOOD - System button provides proper touch target
Button(action: { /* action */ }) {
Image(systemName: "xmark")
}
// GOOD - Custom with proper frame
Image(systemName: "xmark")
.frame(width: 44, height: 44)
.contentShape(Rectangle())
.onTapGesture { /* action */ }
// BAD - Too small
Image(systemName: "xmark")
.font(.system(size: 16))
.onTapGesture { /* action */ }
Safe Areas
Respecting Safe Areas
// GOOD - Content respects safe areas
ScrollView {
content
}
.safeAreaInset(edge: .bottom) {
bottomBar
}
// Full-bleed backgrounds that extend into safe areas
ZStack {
Color.blue.ignoresSafeArea()
VStack {
content // Content still respects safe areas
}
}
Device-Specific Considerations
| Device | Top Safe Area | Bottom Safe Area |
|---|---|---|
| iPhone (notch) | 47pt | 34pt |
| iPhone (Dynamic Island) | 59pt | 34pt |
| iPhone SE | 20pt | 0pt |
| iPad | 20pt | 0pt |
Spacing Scale
Base unit: 4pt
| Size | pt | Use Case |
|---|---|---|
| xs | 4pt | Inline spacing |
| sm | 8pt | Related elements |
| md | 12pt | Group spacing |
| lg | 16pt | Section spacing |
| xl | 20pt | Major divisions |
| 2xl | 24pt | Page margins (compact) |
| 3xl | 32pt | Large gaps |
Standard Margins
| Context | Margin |
|---|---|
| Compact width | 16pt |
| Regular width | 20pt |
| List insets | 16pt leading, 20pt trailing |
DO / DON'T
Property Wrappers
DO: Use @StateObject for owned objects
struct ContentView: View {
@StateObject private var viewModel = ContentViewModel()
var body: some View {
Text(viewModel.text)
}
}
DON'T: Use @ObservedObject for owned objects
struct ContentView: View {
@ObservedObject var viewModel = ContentViewModel() // Re-created on every view update!
var body: some View {
Text(viewModel.text)
}
}
List Performance
DO: Use LazyVStack/List for long content
ScrollView {
LazyVStack {
ForEach(items, id: \.id) { item in
ItemRow(item: item)
}
}
}
DON'T: Use VStack for many items
ScrollView {
VStack { // All items created at once
ForEach(items, id: \.id) { item in
ItemRow(item: item)
}
}
}
View Identity
DO: Provide stable IDs in ForEach
ForEach(items, id: \.id) { item in
ItemRow(item: item)
}
DON'T: Use indices or miss id parameter
ForEach(items.indices, id: \.self) { index in // Breaks on reorder
ItemRow(item: items[index])
}
Type Erasure
DO: Use @ViewBuilder or concrete types
@ViewBuilder
var content: some View {
if condition {
Text("A")
} else {
Image(systemName: "star")
}
}
DON'T: Use AnyView excessively
var content: AnyView { // Kills performance
if condition {
return AnyView(Text("A"))
} else {
return AnyView(Image(systemName: "star"))
}
}
Modifier Order
DO: Background before padding, frame before overlay
Text("Hello")
.padding()
.background(Color.blue) // Includes padding in background
Text("World")
.frame(maxWidth: .infinity)
.overlay(alignment: .trailing) { // Overlay spans full width
Button("Edit") { }
}
DON'T: Wrong order causes unexpected results
Text("Hello")
.background(Color.blue) // Only behind text
.padding() // Padding outside background
Text("World")
.overlay(alignment: .trailing) { // Overlay only covers text width
Button("Edit") { }
}
.frame(maxWidth: .infinity)
Severity Levels
When auditing, classify issues as:
| Level | Icon | Description | Example |
|---|---|---|---|
| Critical | 🔴 | Blocks accessibility or causes crashes | Missing accessibilityLabel |
| Important | 🟠| Violates HIG or hurts UX | Touch target < 44pt |
| Warning | 🟡 | Suboptimal but functional | Hardcoded color |
| Suggestion | 🔵 | Polish opportunities | Non-standard spacing |
Expert References
SwiftUI Architecture & Patterns
- WWDC 2021: Demystify SwiftUI - View identity, lifetime, dependencies
- Property Wrapper Decision Tree - @State, @StateObject, @Observable, @Binding rules + scanner patterns
- IceCubesApp Production Patterns - Real-world patterns from largest open-source SwiftUI app
Modern iOS 18 Features
- iOS 18 Materials & Glass Effects - Material API, glass morphism, liquid glass design
Anti-Patterns & Scanning
- Anti-Patterns Database - 23 patterns with regex scanners for automated audits
Weekly Installs
4
Repository
moonlightbyte/kilnFirst Seen
5 days ago
Security Audits
Installed on
opencode4
gemini-cli4
github-copilot4
codex4
amp4
cline4