947 lines
20 KiB
Markdown
947 lines
20 KiB
Markdown
---
|
|
name: apple-hig-designer
|
|
description: Design iOS apps following Apple's Human Interface Guidelines. Generate native components, validate designs, and ensure accessibility compliance for iPhone, iPad, and Apple Watch.
|
|
---
|
|
|
|
# Apple HIG Designer
|
|
|
|
Design beautiful, native iOS apps following Apple's Human Interface Guidelines (HIG). Create accessible, intuitive interfaces with native components, proper typography, semantic colors, and Apple's design principles.
|
|
|
|
## What This Skill Does
|
|
|
|
Helps you design and build iOS apps that feel native and follow Apple's guidelines:
|
|
- **Generate iOS Components** - Create SwiftUI and UIKit components
|
|
- **Validate Designs** - Check compliance with Apple HIG
|
|
- **Ensure Accessibility** - VoiceOver, Dynamic Type, color contrast
|
|
- **Apply Design Principles** - Clarity, Deference, Depth
|
|
- **Use Semantic Colors** - Automatic dark mode support
|
|
- **Implement Typography** - San Francisco font system
|
|
- **Follow Spacing** - 8pt grid system and safe areas
|
|
|
|
## Apple's Design Principles
|
|
|
|
### 1. Clarity
|
|
|
|
**Make content clear and focused.**
|
|
|
|
Text is legible at every size, icons are precise and lucid, adornments are subtle and appropriate, and a focus on functionality drives the design.
|
|
|
|
```swift
|
|
// ✅ Clear, focused content
|
|
Text("Welcome back, Sarah")
|
|
.font(.title)
|
|
.foregroundColor(.primary)
|
|
|
|
// ❌ Unclear, cluttered
|
|
Text("Welcome back, Sarah!!!")
|
|
.font(.title)
|
|
.foregroundColor(.red)
|
|
.background(.yellow)
|
|
.overlay(Image(systemName: "star.fill"))
|
|
```
|
|
|
|
### 2. Deference
|
|
|
|
**UI helps people understand and interact with content, but never competes with it.**
|
|
|
|
The interface defers to content, using a light visual treatment that keeps focus on the content and gives the content room to breathe.
|
|
|
|
```swift
|
|
// ✅ Content-focused
|
|
VStack(alignment: .leading, spacing: 8) {
|
|
Text("Article Title")
|
|
.font(.headline)
|
|
Text("Article content goes here...")
|
|
.font(.body)
|
|
.foregroundColor(.secondary)
|
|
}
|
|
.padding()
|
|
|
|
// ❌ Distracting UI
|
|
VStack(spacing: 8) {
|
|
Text("Article Title")
|
|
.font(.headline)
|
|
.foregroundColor(.white)
|
|
.background(.blue)
|
|
.border(.red, width: 3)
|
|
}
|
|
```
|
|
|
|
### 3. Depth
|
|
|
|
**Visual layers and realistic motion convey hierarchy and help people understand relationships.**
|
|
|
|
Distinct visual layers and realistic motion impart vitality and facilitate understanding. Touch and discoverability heighten delight and enable access to functionality without losing context.
|
|
|
|
```swift
|
|
// ✅ Clear depth hierarchy
|
|
ZStack {
|
|
Color(.systemBackground)
|
|
|
|
VStack {
|
|
// Card with elevation
|
|
CardView()
|
|
.shadow(radius: 8)
|
|
}
|
|
}
|
|
|
|
// Using blur for depth
|
|
Text("Content")
|
|
.background(.ultraThinMaterial)
|
|
```
|
|
|
|
## iOS UI Components
|
|
|
|
### Navigation Patterns
|
|
|
|
#### 1. Navigation Bar
|
|
|
|
**Top bar for navigation and actions.**
|
|
|
|
```swift
|
|
NavigationStack {
|
|
List {
|
|
Text("Item 1")
|
|
Text("Item 2")
|
|
}
|
|
.navigationTitle("Title")
|
|
.navigationBarTitleDisplayMode(.large)
|
|
.toolbar {
|
|
ToolbarItem(placement: .navigationBarTrailing) {
|
|
Button("Add") {
|
|
// Action
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Guidelines:**
|
|
- Use large titles for top-level views
|
|
- Use inline titles for detail views
|
|
- Keep actions relevant to current context
|
|
- Maximum 2-3 toolbar items
|
|
|
|
#### 2. Tab Bar
|
|
|
|
**Bottom navigation for top-level destinations.**
|
|
|
|
```swift
|
|
TabView {
|
|
HomeView()
|
|
.tabItem {
|
|
Label("Home", systemImage: "house")
|
|
}
|
|
|
|
SearchView()
|
|
.tabItem {
|
|
Label("Search", systemImage: "magnifyingglass")
|
|
}
|
|
|
|
ProfileView()
|
|
.tabItem {
|
|
Label("Profile", systemImage: "person")
|
|
}
|
|
}
|
|
```
|
|
|
|
**Guidelines:**
|
|
- 3-5 tabs maximum
|
|
- Use SF Symbols for icons
|
|
- Labels should be concise (one word)
|
|
- Never hide or disable tabs
|
|
- Don't use tab bar with toolbar in same view
|
|
|
|
#### 3. List
|
|
|
|
**Scrollable list of items.**
|
|
|
|
```swift
|
|
List {
|
|
Section("Today") {
|
|
ForEach(items) { item in
|
|
NavigationLink {
|
|
DetailView(item: item)
|
|
} label: {
|
|
HStack {
|
|
Image(systemName: item.icon)
|
|
.foregroundColor(.accentColor)
|
|
Text(item.title)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.listStyle(.insetGrouped)
|
|
```
|
|
|
|
**List Styles:**
|
|
- `.plain` - Edge-to-edge rows
|
|
- `.insetGrouped` - Rounded, inset sections (iOS default)
|
|
- `.sidebar` - For navigation sidebars
|
|
|
|
#### 4. Sheet (Modal)
|
|
|
|
**Present content modally.**
|
|
|
|
```swift
|
|
struct ContentView: View {
|
|
@State private var showSheet = false
|
|
|
|
var body: some View {
|
|
Button("Show Details") {
|
|
showSheet = true
|
|
}
|
|
.sheet(isPresented: $showSheet) {
|
|
DetailView()
|
|
.presentationDetents([.medium, .large])
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Sheet Detents:**
|
|
- `.medium` - Half screen
|
|
- `.large` - Full screen
|
|
- Custom heights available
|
|
|
|
### Form Controls
|
|
|
|
#### 1. Button
|
|
|
|
**Primary action control.**
|
|
|
|
```swift
|
|
// Filled button (primary action)
|
|
Button("Continue") {
|
|
// Action
|
|
}
|
|
.buttonStyle(.borderedProminent)
|
|
|
|
// Bordered button (secondary action)
|
|
Button("Cancel") {
|
|
// Action
|
|
}
|
|
.buttonStyle(.bordered)
|
|
|
|
// Plain button (tertiary action)
|
|
Button("Learn More") {
|
|
// Action
|
|
}
|
|
.buttonStyle(.plain)
|
|
```
|
|
|
|
**Button Hierarchy:**
|
|
1. **Prominent** - Primary action (one per screen)
|
|
2. **Bordered** - Secondary actions
|
|
3. **Plain** - Tertiary actions, links
|
|
|
|
**Guidelines:**
|
|
- Minimum tap target: 44x44 points
|
|
- Use verbs for button labels
|
|
- Make destructive actions require confirmation
|
|
|
|
#### 2. TextField
|
|
|
|
**Text input control.**
|
|
|
|
```swift
|
|
@State private var username = ""
|
|
@State private var password = ""
|
|
|
|
VStack(alignment: .leading, spacing: 16) {
|
|
// Standard text field
|
|
TextField("Username", text: $username)
|
|
.textFieldStyle(.roundedBorder)
|
|
.textContentType(.username)
|
|
.textInputAutocapitalization(.never)
|
|
.autocorrectionDisabled()
|
|
|
|
// Secure field
|
|
SecureField("Password", text: $password)
|
|
.textFieldStyle(.roundedBorder)
|
|
.textContentType(.password)
|
|
}
|
|
```
|
|
|
|
**Text Content Types:**
|
|
- `.username` - Username field
|
|
- `.password` - Password field
|
|
- `.emailAddress` - Email field
|
|
- `.telephoneNumber` - Phone number
|
|
- `.creditCardNumber` - Credit card
|
|
|
|
#### 3. Toggle
|
|
|
|
**Boolean control (switch).**
|
|
|
|
```swift
|
|
@State private var isEnabled = false
|
|
|
|
Toggle("Enable notifications", isOn: $isEnabled)
|
|
.toggleStyle(.switch)
|
|
```
|
|
|
|
**Guidelines:**
|
|
- Label describes what the toggle controls
|
|
- Effect should be immediate
|
|
- Use for binary choices only
|
|
|
|
#### 4. Picker
|
|
|
|
**Selection control.**
|
|
|
|
```swift
|
|
@State private var selectedSize = "Medium"
|
|
let sizes = ["Small", "Medium", "Large"]
|
|
|
|
// Menu style
|
|
Picker("Size", selection: $selectedSize) {
|
|
ForEach(sizes, id: \.self) { size in
|
|
Text(size).tag(size)
|
|
}
|
|
}
|
|
.pickerStyle(.menu)
|
|
|
|
// Segmented style (for 2-5 options)
|
|
Picker("Size", selection: $selectedSize) {
|
|
ForEach(sizes, id: \.self) { size in
|
|
Text(size).tag(size)
|
|
}
|
|
}
|
|
.pickerStyle(.segmented)
|
|
```
|
|
|
|
**Picker Styles:**
|
|
- `.menu` - Dropdown menu (default)
|
|
- `.segmented` - Segmented control (2-5 options)
|
|
- `.wheel` - Scrollable wheel
|
|
- `.inline` - Inline list (in forms)
|
|
|
|
### Cards and Containers
|
|
|
|
#### Card View
|
|
|
|
```swift
|
|
struct CardView: View {
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
Text("Title")
|
|
.font(.headline)
|
|
|
|
Text("Description goes here with some details about the content.")
|
|
.font(.subheadline)
|
|
.foregroundColor(.secondary)
|
|
.lineLimit(2)
|
|
|
|
Spacer()
|
|
|
|
Button("Action") {
|
|
// Action
|
|
}
|
|
.buttonStyle(.borderedProminent)
|
|
}
|
|
.padding()
|
|
.frame(width: 300, height: 200)
|
|
.background(Color(.systemBackground))
|
|
.cornerRadius(12)
|
|
.shadow(color: .black.opacity(0.1), radius: 8, x: 0, y: 4)
|
|
}
|
|
}
|
|
```
|
|
|
|
## Typography
|
|
|
|
### San Francisco Font System
|
|
|
|
Apple's system font designed for optimal legibility.
|
|
|
|
```swift
|
|
// Dynamic Type text styles
|
|
Text("Large Title").font(.largeTitle) // 34pt
|
|
Text("Title").font(.title) // 28pt
|
|
Text("Title 2").font(.title2) // 22pt
|
|
Text("Title 3").font(.title3) // 20pt
|
|
Text("Headline").font(.headline) // 17pt semibold
|
|
Text("Body").font(.body) // 17pt regular
|
|
Text("Callout").font(.callout) // 16pt
|
|
Text("Subheadline").font(.subheadline) // 15pt
|
|
Text("Footnote").font(.footnote) // 13pt
|
|
Text("Caption").font(.caption) // 12pt
|
|
Text("Caption 2").font(.caption2) // 11pt
|
|
```
|
|
|
|
### Custom Fonts with Dynamic Type
|
|
|
|
```swift
|
|
// Custom font that scales with Dynamic Type
|
|
Text("Custom Text")
|
|
.font(.custom("YourFont-Regular", size: 17, relativeTo: .body))
|
|
```
|
|
|
|
### Font Weights
|
|
|
|
```swift
|
|
Text("Light").fontWeight(.light)
|
|
Text("Regular").fontWeight(.regular)
|
|
Text("Medium").fontWeight(.medium)
|
|
Text("Semibold").fontWeight(.semibold)
|
|
Text("Bold").fontWeight(.bold)
|
|
Text("Heavy").fontWeight(.heavy)
|
|
```
|
|
|
|
### Typography Guidelines
|
|
|
|
**Do:**
|
|
- ✅ Use system font (San Francisco) for consistency
|
|
- ✅ Support Dynamic Type for accessibility
|
|
- ✅ Use semantic text styles (.headline, .body, etc.)
|
|
- ✅ Minimum body text: 17pt
|
|
- ✅ Line spacing: 120-145% of font size
|
|
|
|
**Don't:**
|
|
- ❌ Use too many font sizes (stick to system styles)
|
|
- ❌ Make text smaller than 11pt
|
|
- ❌ Use all caps for long text
|
|
- ❌ Disable Dynamic Type
|
|
|
|
## Colors
|
|
|
|
### Semantic Colors
|
|
|
|
**Colors that automatically adapt to light/dark mode.**
|
|
|
|
```swift
|
|
// UI Element Colors
|
|
Color(.label) // Primary text
|
|
Color(.secondaryLabel) // Secondary text
|
|
Color(.tertiaryLabel) // Tertiary text
|
|
Color(.quaternaryLabel) // Watermark text
|
|
|
|
Color(.systemBackground) // Primary background
|
|
Color(.secondarySystemBackground) // Secondary background
|
|
Color(.tertiarySystemBackground) // Tertiary background
|
|
|
|
Color(.systemFill) // Fill colors
|
|
Color(.secondarySystemFill)
|
|
Color(.tertiarySystemFill)
|
|
Color(.quaternarySystemFill)
|
|
|
|
Color(.separator) // Separator lines
|
|
Color(.opaqueSeparator) // Non-transparent separator
|
|
```
|
|
|
|
### System Colors
|
|
|
|
```swift
|
|
// Standard system colors (adapt to dark mode)
|
|
Color(.systemRed)
|
|
Color(.systemOrange)
|
|
Color(.systemYellow)
|
|
Color(.systemGreen)
|
|
Color(.systemMint)
|
|
Color(.systemTeal)
|
|
Color(.systemCyan)
|
|
Color(.systemBlue)
|
|
Color(.systemIndigo)
|
|
Color(.systemPurple)
|
|
Color(.systemPink)
|
|
Color(.systemBrown)
|
|
Color(.systemGray)
|
|
```
|
|
|
|
### Custom Colors with Dark Mode
|
|
|
|
```swift
|
|
// Define adaptive color
|
|
extension Color {
|
|
static let customBackground = Color("CustomBackground")
|
|
}
|
|
|
|
// In Assets.xcassets, create color set with:
|
|
// - Any Appearance: #FFFFFF
|
|
// - Dark Appearance: #000000
|
|
```
|
|
|
|
### Color Contrast Guidelines
|
|
|
|
**WCAG AA Compliance:**
|
|
- Normal text: 4.5:1 contrast ratio minimum
|
|
- Large text (24pt+): 3:1 contrast ratio minimum
|
|
- UI components: 3:1 contrast ratio
|
|
|
|
**Custom colors:**
|
|
- Test with Increase Contrast enabled
|
|
- Aim for 7:1 for critical text
|
|
- Provide sufficient contrast in both modes
|
|
|
|
## Spacing and Layout
|
|
|
|
### 8-Point Grid System
|
|
|
|
**All spacing should be multiples of 8.**
|
|
|
|
```swift
|
|
// Spacing values
|
|
.padding(8) // 8pt
|
|
.padding(16) // 16pt (standard)
|
|
.padding(24) // 24pt
|
|
.padding(32) // 32pt
|
|
.padding(40) // 40pt
|
|
.padding(48) // 48pt
|
|
|
|
// Edge-specific padding
|
|
.padding(.horizontal, 16)
|
|
.padding(.vertical, 24)
|
|
.padding(.top, 16)
|
|
.padding(.bottom, 16)
|
|
```
|
|
|
|
### Safe Areas
|
|
|
|
**Respect device safe areas.**
|
|
|
|
```swift
|
|
// Content within safe area (default)
|
|
VStack {
|
|
Text("Content")
|
|
}
|
|
|
|
// Extend beyond safe area
|
|
VStack {
|
|
Color.blue
|
|
}
|
|
.ignoresSafeArea()
|
|
|
|
// Extend top only
|
|
VStack {
|
|
Color.blue
|
|
}
|
|
.ignoresSafeArea(edges: .top)
|
|
```
|
|
|
|
### Touch Targets
|
|
|
|
**Minimum interactive size: 44x44 points.**
|
|
|
|
```swift
|
|
Button("Tap") {
|
|
// Action
|
|
}
|
|
.frame(minWidth: 44, minHeight: 44)
|
|
```
|
|
|
|
### Spacing Guidelines
|
|
|
|
```swift
|
|
// Component spacing
|
|
VStack(spacing: 8) { // Tight spacing
|
|
Text("Line 1")
|
|
Text("Line 2")
|
|
}
|
|
|
|
VStack(spacing: 16) { // Standard spacing
|
|
Text("Section 1")
|
|
Text("Section 2")
|
|
}
|
|
|
|
VStack(spacing: 24) { // Loose spacing
|
|
SectionView()
|
|
SectionView()
|
|
}
|
|
```
|
|
|
|
## Accessibility
|
|
|
|
### VoiceOver Support
|
|
|
|
**Screen reader for blind and low-vision users.**
|
|
|
|
```swift
|
|
// Accessible label
|
|
Image(systemName: "heart.fill")
|
|
.accessibilityLabel("Favorite")
|
|
|
|
// Accessible value
|
|
Slider(value: $volume)
|
|
.accessibilityLabel("Volume")
|
|
.accessibilityValue("\(Int(volume * 100))%")
|
|
|
|
// Accessible hint
|
|
Button("Share") {
|
|
share()
|
|
}
|
|
.accessibilityHint("Shares this item with others")
|
|
|
|
// Group elements
|
|
HStack {
|
|
Image(systemName: "person")
|
|
Text("John Doe")
|
|
}
|
|
.accessibilityElement(children: .combine)
|
|
|
|
// Hidden from VoiceOver
|
|
Image("decorative")
|
|
.accessibilityHidden(true)
|
|
```
|
|
|
|
### Dynamic Type
|
|
|
|
**Support user's preferred text size.**
|
|
|
|
```swift
|
|
// Automatically supported with system fonts
|
|
Text("This text scales")
|
|
.font(.body)
|
|
|
|
// Limit scaling (if necessary)
|
|
Text("This text has limits")
|
|
.font(.body)
|
|
.dynamicTypeSize(...DynamicTypeSize.xxxLarge)
|
|
|
|
// Custom font with Dynamic Type
|
|
Text("Custom font")
|
|
.font(.custom("YourFont", size: 17, relativeTo: .body))
|
|
```
|
|
|
|
### Color Blindness
|
|
|
|
**Design for color-blind users.**
|
|
|
|
```swift
|
|
// Don't rely on color alone
|
|
HStack {
|
|
Image(systemName: "checkmark.circle.fill")
|
|
.foregroundColor(.green)
|
|
Text("Success")
|
|
}
|
|
|
|
// Not just color
|
|
Circle()
|
|
.fill(.green)
|
|
// ❌ Color only
|
|
|
|
// Better with shape/icon
|
|
HStack {
|
|
Image(systemName: "checkmark.circle.fill")
|
|
Circle().fill(.green)
|
|
}
|
|
// ✅ Color + shape
|
|
```
|
|
|
|
### Reduce Motion
|
|
|
|
**Respect user's motion preferences.**
|
|
|
|
```swift
|
|
@Environment(\.accessibilityReduceMotion) var reduceMotion
|
|
|
|
var animation: Animation {
|
|
reduceMotion ? .none : .spring()
|
|
}
|
|
|
|
Button("Animate") {
|
|
withAnimation(animation) {
|
|
// Animate
|
|
}
|
|
}
|
|
```
|
|
|
|
### Increase Contrast
|
|
|
|
**Support high contrast mode.**
|
|
|
|
```swift
|
|
@Environment(\.colorSchemeContrast) var contrast
|
|
|
|
var textColor: Color {
|
|
contrast == .increased ? .primary : .secondary
|
|
}
|
|
|
|
Text("Content")
|
|
.foregroundColor(textColor)
|
|
```
|
|
|
|
## Dark Mode
|
|
|
|
**Support both light and dark appearances.**
|
|
|
|
### Automatic Support
|
|
|
|
```swift
|
|
// Use semantic colors (automatic)
|
|
Color(.label) // Adapts automatically
|
|
Color(.systemBackground) // Adapts automatically
|
|
```
|
|
|
|
### Testing Dark Mode
|
|
|
|
```swift
|
|
// Preview both modes
|
|
struct ContentView_Previews: PreviewProvider {
|
|
static var previews: some View {
|
|
ContentView()
|
|
.preferredColorScheme(.light)
|
|
|
|
ContentView()
|
|
.preferredColorScheme(.dark)
|
|
}
|
|
}
|
|
```
|
|
|
|
### Dark Mode Guidelines
|
|
|
|
**Do:**
|
|
- ✅ Use semantic colors
|
|
- ✅ Test with Increase Contrast
|
|
- ✅ Test with Reduce Transparency
|
|
- ✅ Ensure sufficient contrast in both modes
|
|
|
|
**Don't:**
|
|
- ❌ Use pure black (#000000) - use systemBackground
|
|
- ❌ Invert colors automatically
|
|
- ❌ Assume user preference
|
|
|
|
## SF Symbols
|
|
|
|
**Apple's icon system (3000+ symbols).**
|
|
|
|
```swift
|
|
// Basic symbol
|
|
Image(systemName: "heart")
|
|
|
|
// Colored symbol
|
|
Image(systemName: "heart.fill")
|
|
.foregroundColor(.red)
|
|
|
|
// Sized symbol
|
|
Image(systemName: "heart")
|
|
.imageScale(.large)
|
|
|
|
// Font-based sizing
|
|
Image(systemName: "heart")
|
|
.font(.title)
|
|
|
|
// Multicolor symbols
|
|
Image(systemName: "person.crop.circle.fill.badge.checkmark")
|
|
.symbolRenderingMode(.multicolor)
|
|
|
|
// Hierarchical rendering
|
|
Image(systemName: "heart.fill")
|
|
.symbolRenderingMode(.hierarchical)
|
|
.foregroundColor(.red)
|
|
```
|
|
|
|
### SF Symbols Guidelines
|
|
|
|
- Use system symbols when available
|
|
- Maintain visual weight consistency
|
|
- Use multicolor for semantic meaning
|
|
- Size appropriately for context
|
|
|
|
## App Icons
|
|
|
|
### Icon Sizes
|
|
|
|
```
|
|
iOS:
|
|
- 1024x1024 (App Store)
|
|
- 180x180 (iPhone @3x)
|
|
- 120x120 (iPhone @2x)
|
|
- 167x167 (iPad Pro)
|
|
- 152x152 (iPad @2x)
|
|
|
|
watchOS:
|
|
- 1024x1024 (App Store)
|
|
- 196x196 (49mm)
|
|
- 216x216 (45mm)
|
|
```
|
|
|
|
### Icon Design Guidelines
|
|
|
|
**Do:**
|
|
- ✅ Use simple, recognizable shapes
|
|
- ✅ Fill entire icon space
|
|
- ✅ Test on device (not just mockups)
|
|
- ✅ Use consistent visual style
|
|
|
|
**Don't:**
|
|
- ❌ Include text (very small)
|
|
- ❌ Use photos
|
|
- ❌ Replicate Apple hardware
|
|
- ❌ Use translucency
|
|
|
|
## Animation and Motion
|
|
|
|
### Standard Animations
|
|
|
|
```swift
|
|
// Spring animation (natural, bouncy)
|
|
withAnimation(.spring()) {
|
|
offset = 100
|
|
}
|
|
|
|
// Linear animation
|
|
withAnimation(.linear(duration: 0.3)) {
|
|
opacity = 0
|
|
}
|
|
|
|
// Ease in/out
|
|
withAnimation(.easeInOut(duration: 0.3)) {
|
|
scale = 1.2
|
|
}
|
|
```
|
|
|
|
### Gesture-Driven
|
|
|
|
```swift
|
|
@State private var offset = CGSize.zero
|
|
|
|
var body: some View {
|
|
Circle()
|
|
.offset(offset)
|
|
.gesture(
|
|
DragGesture()
|
|
.onChanged { value in
|
|
offset = value.translation
|
|
}
|
|
.onEnded { _ in
|
|
withAnimation(.spring()) {
|
|
offset = .zero
|
|
}
|
|
}
|
|
)
|
|
}
|
|
```
|
|
|
|
### Motion Guidelines
|
|
|
|
- Keep animations under 0.3 seconds
|
|
- Use spring animations for interactive elements
|
|
- Respect Reduce Motion setting
|
|
- Provide visual feedback for all interactions
|
|
|
|
## Best Practices
|
|
|
|
### Navigation
|
|
|
|
- **Hierarchical** - Use NavigationStack for drilldown
|
|
- **Flat** - Use TabView for peer destinations
|
|
- **Content-Driven** - Use for media apps
|
|
|
|
### Feedback
|
|
|
|
- **Visual** - Highlight on tap
|
|
- **Haptic** - Use UIImpactFeedbackGenerator
|
|
- **Audio** - Use system sounds sparingly
|
|
|
|
### Loading States
|
|
|
|
```swift
|
|
struct LoadingView: View {
|
|
var body: some View {
|
|
VStack {
|
|
ProgressView()
|
|
.scaleEffect(1.5)
|
|
Text("Loading...")
|
|
.font(.caption)
|
|
.foregroundColor(.secondary)
|
|
.padding(.top)
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Error States
|
|
|
|
```swift
|
|
struct ErrorView: View {
|
|
let message: String
|
|
let retry: () -> Void
|
|
|
|
var body: some View {
|
|
VStack(spacing: 16) {
|
|
Image(systemName: "exclamationmark.triangle")
|
|
.font(.system(size: 48))
|
|
.foregroundColor(.orange)
|
|
|
|
Text("Something went wrong")
|
|
.font(.headline)
|
|
|
|
Text(message)
|
|
.font(.subheadline)
|
|
.foregroundColor(.secondary)
|
|
.multilineTextAlignment(.center)
|
|
|
|
Button("Try Again") {
|
|
retry()
|
|
}
|
|
.buttonStyle(.borderedProminent)
|
|
}
|
|
.padding()
|
|
}
|
|
}
|
|
```
|
|
|
|
### Empty States
|
|
|
|
```swift
|
|
struct EmptyStateView: View {
|
|
var body: some View {
|
|
VStack(spacing: 16) {
|
|
Image(systemName: "tray")
|
|
.font(.system(size: 64))
|
|
.foregroundColor(.secondary)
|
|
|
|
Text("No Items")
|
|
.font(.title2)
|
|
|
|
Text("Your items will appear here")
|
|
.font(.subheadline)
|
|
.foregroundColor(.secondary)
|
|
|
|
Button("Add Item") {
|
|
// Action
|
|
}
|
|
.buttonStyle(.borderedProminent)
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## Platform Considerations
|
|
|
|
### iPhone
|
|
|
|
- Design for various sizes (SE, Pro, Pro Max)
|
|
- Support portrait and landscape
|
|
- Use safe areas for notch/Dynamic Island
|
|
- Consider one-handed use
|
|
|
|
### iPad
|
|
|
|
- Support multitasking (Split View, Slide Over)
|
|
- Use sidebars for navigation
|
|
- Adapt to larger screen (don't just scale)
|
|
- Consider keyboard shortcuts
|
|
- Support external displays
|
|
|
|
### Apple Watch
|
|
|
|
- Glanceable information
|
|
- Large touch targets (>44pt)
|
|
- Minimal interaction required
|
|
- Use Digital Crown for scrolling
|
|
- Support Always-On display
|
|
|
|
## Resources
|
|
|
|
- [Apple HIG Official](https://developer.apple.com/design/human-interface-guidelines/)
|
|
- [SF Symbols App](https://developer.apple.com/sf-symbols/)
|
|
- [WWDC Videos](https://developer.apple.com/videos/)
|
|
- [Apple Design Resources](https://developer.apple.com/design/resources/)
|
|
|
|
---
|
|
|
|
**"Design is not just what it looks like and feels like. Design is how it works." - Steve Jobs**
|