Skip to content

SwiftUI Cheat Sheet

Overview

SwiftUI is Apple’s modern declarative framework for building user interfaces across all Apple platforms. Introduced at WWDC 2019, it allows developers to describe UI layouts and behaviors using Swift code with a syntax that reads like a description of the desired interface. SwiftUI handles the rendering, animation, and state management automatically, drastically reducing the amount of boilerplate code compared to UIKit or AppKit. The framework uses a reactive data flow model where views automatically update when the underlying state changes.

SwiftUI integrates deeply with Xcode, providing real-time previews that update as you write code. It supports all Apple platforms from a single codebase, though platform-specific adaptations are straightforward to implement. The framework includes built-in support for accessibility, localization, dark mode, and dynamic type. SwiftUI views are lightweight value types (structs) that conform to the View protocol, making them efficient to create and destroy as the UI updates.

Installation

# SwiftUI requires Xcode (macOS only)
# Install Xcode from Mac App Store or:
xcode-select --install

# Verify Xcode installation
xcodebuild -version

# Minimum requirements:
# - Xcode 15+ for latest SwiftUI features
# - macOS 14+ (Sonoma) for development
# - iOS 17+ / macOS 14+ deployment targets for latest APIs
# Create a new SwiftUI project via command line
mkdir MySwiftUIApp && cd MySwiftUIApp

# Or use Xcode: File > New > Project > App
# Select "SwiftUI" for Interface option

Basic View Structure

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Text("Hello, World!")
                .font(.title)
                .foregroundColor(.blue)
            
            Image(systemName: "star.fill")
                .imageScale(.large)
                .foregroundStyle(.tint)
        }
        .padding()
    }
}

#Preview {
    ContentView()
}

Common Views and Modifiers

ViewDescription
Text("Hello")Display static or dynamic text
Image(systemName: "star")SF Symbols or asset images
Button("Tap") { action() }Tappable button with action
TextField("Placeholder", text: $value)Text input field
SecureField("Password", text: $pwd)Password input
Toggle("Label", isOn: $flag)Boolean toggle switch
Slider(value: $val, in: 0...100)Numeric slider
Picker("Label", selection: $sel)Selection picker
DatePicker("Date", selection: $date)Date/time picker
ProgressView(value: 0.5)Progress indicator
ModifierDescription
.font(.title)Set text font style
.foregroundColor(.red)Set foreground color
.background(.blue)Set background color
.padding()Add padding around view
.frame(width: 200, height: 100)Set explicit dimensions
.cornerRadius(10)Round corners
.shadow(radius: 5)Add drop shadow
.opacity(0.8)Set transparency
.rotationEffect(.degrees(45))Rotate view
.scaleEffect(1.5)Scale view size

Layout Containers

// Vertical stack
VStack(alignment: .leading, spacing: 10) {
    Text("First")
    Text("Second")
    Text("Third")
}

// Horizontal stack
HStack(spacing: 16) {
    Image(systemName: "person")
    Text("Username")
    Spacer()
    Text("Edit")
}

// Overlay / ZStack (layered)
ZStack {
    Color.blue.ignoresSafeArea()
    VStack {
        Text("Overlaid Content")
            .foregroundColor(.white)
    }
}

// Grid layout (iOS 16+)
Grid {
    GridRow {
        Text("Row 1, Col 1")
        Text("Row 1, Col 2")
    }
    GridRow {
        Text("Row 2, Col 1")
        Text("Row 2, Col 2")
    }
}

// Lazy stacks for performance
ScrollView {
    LazyVStack {
        ForEach(0..<1000) { index in
            Text("Row \(index)")
        }
    }
}

State Management

struct CounterView: View {
    // Local state
    @State private var count = 0
    
    var body: some View {
        VStack {
            Text("Count: \(count)")
            Button("Increment") {
                count += 1
            }
        }
    }
}

// Binding: pass state to child views
struct ParentView: View {
    @State private var name = ""
    
    var body: some View {
        ChildView(name: $name)
    }
}

struct ChildView: View {
    @Binding var name: String
    
    var body: some View {
        TextField("Enter name", text: $name)
    }
}

// ObservableObject for shared state
@Observable
class UserSettings {
    var username = ""
    var isLoggedIn = false
    var theme: Theme = .light
}

struct SettingsView: View {
    @State private var settings = UserSettings()
    
    var body: some View {
        Toggle("Logged In", isOn: $settings.isLoggedIn)
    }
}

// Environment for dependency injection
struct MyApp: App {
    @State private var settings = UserSettings()
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(settings)
        }
    }
}

struct ContentView: View {
    @Environment(UserSettings.self) var settings
    
    var body: some View {
        Text(settings.username)
    }
}
// NavigationStack (iOS 16+)
NavigationStack {
    List {
        NavigationLink("Detail View") {
            DetailView()
        }
        NavigationLink(value: item) {
            Text(item.name)
        }
    }
    .navigationTitle("My App")
    .navigationDestination(for: Item.self) { item in
        ItemDetailView(item: item)
    }
}

// Tab-based navigation
TabView {
    HomeView()
        .tabItem {
            Label("Home", systemImage: "house")
        }
    
    SettingsView()
        .tabItem {
            Label("Settings", systemImage: "gear")
        }
}

// Sheet / Modal presentation
struct MainView: View {
    @State private var showSheet = false
    
    var body: some View {
        Button("Show Sheet") {
            showSheet = true
        }
        .sheet(isPresented: $showSheet) {
            SheetContent()
        }
    }
}

// Alert
.alert("Confirm Action", isPresented: $showAlert) {
    Button("Cancel", role: .cancel) { }
    Button("Delete", role: .destructive) {
        deleteItem()
    }
} message: {
    Text("This action cannot be undone.")
}

Lists and Data Display

struct ItemListView: View {
    @State private var items = ["Apple", "Banana", "Cherry"]
    
    var body: some View {
        List {
            ForEach(items, id: \.self) { item in
                Text(item)
            }
            .onDelete(perform: delete)
            .onMove(perform: move)
        }
        .toolbar {
            EditButton()
        }
    }
    
    func delete(at offsets: IndexSet) {
        items.remove(atOffsets: offsets)
    }
    
    func move(from source: IndexSet, to destination: Int) {
        items.move(fromOffsets: source, toOffset: destination)
    }
}

// Sections in Lists
List {
    Section("Fruits") {
        Text("Apple")
        Text("Banana")
    }
    Section("Vegetables") {
        Text("Carrot")
        Text("Broccoli")
    }
}

Animations

struct AnimationExample: View {
    @State private var isExpanded = false
    
    var body: some View {
        VStack {
            RoundedRectangle(cornerRadius: 20)
                .fill(.blue)
                .frame(
                    width: isExpanded ? 300 : 100,
                    height: isExpanded ? 300 : 100
                )
                .animation(.spring(duration: 0.5), value: isExpanded)
            
            Button("Toggle") {
                isExpanded.toggle()
            }
        }
    }
}

// Explicit animation
withAnimation(.easeInOut(duration: 0.3)) {
    showDetail.toggle()
}

// Transition effects
if showContent {
    Text("Appearing!")
        .transition(.slide)
}

// Matched geometry for hero animations
@Namespace private var animation

if isExpanded {
    DetailView()
        .matchedGeometryEffect(id: "hero", in: animation)
} else {
    ThumbnailView()
        .matchedGeometryEffect(id: "hero", in: animation)
}

Configuration and App Lifecycle

@main
struct MyApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .onAppear {
                    // App startup logic
                }
        }
        
        // macOS: additional window types
        #if os(macOS)
        Settings {
            SettingsView()
        }
        #endif
    }
}

// Scene phases
struct ContentView: View {
    @Environment(\.scenePhase) var scenePhase
    
    var body: some View {
        Text("Hello")
            .onChange(of: scenePhase) { _, newPhase in
                switch newPhase {
                case .active: print("App active")
                case .inactive: print("App inactive")
                case .background: print("App backgrounded")
                @unknown default: break
                }
            }
    }
}

Advanced Usage

// Custom ViewModifier
struct CardModifier: ViewModifier {
    func body(content: Content) -> some View {
        content
            .padding()
            .background(.white)
            .cornerRadius(12)
            .shadow(radius: 4)
    }
}

extension View {
    func cardStyle() -> some View {
        modifier(CardModifier())
    }
}

// Usage
Text("Card Content").cardStyle()

// Custom PreferenceKey for child-to-parent communication
struct SizePreferenceKey: PreferenceKey {
    static var defaultValue: CGSize = .zero
    static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
        value = nextValue()
    }
}

// Async data loading
struct AsyncDataView: View {
    @State private var items: [Item] = []
    
    var body: some View {
        List(items) { item in
            Text(item.name)
        }
        .task {
            do {
                items = try await fetchItems()
            } catch {
                print("Error: \(error)")
            }
        }
    }
}

// Custom shapes
struct Hexagon: Shape {
    func path(in rect: CGRect) -> Path {
        var path = Path()
        let center = CGPoint(x: rect.midX, y: rect.midY)
        let radius = min(rect.width, rect.height) / 2
        for i in 0..<6 {
            let angle = Angle(degrees: Double(i) * 60 - 90)
            let point = CGPoint(
                x: center.x + radius * cos(angle.radians),
                y: center.y + radius * sin(angle.radians)
            )
            if i == 0 { path.move(to: point) }
            else { path.addLine(to: point) }
        }
        path.closeSubpath()
        return path
    }
}

Troubleshooting

IssueSolution
Preview not updatingClean build folder: Cmd+Shift+K, then rebuild
”Type does not conform to View”Ensure body returns a single view; wrap in Group or container
State not updating UIUse @State for value types, @Observable for reference types
Navigation back button missingEnsure view is inside NavigationStack
List performance slowUse LazyVStack inside ScrollView instead of List for large datasets
Dark mode colors wrongUse semantic colors like .primary, .secondary instead of hardcoded values
”Cannot convert value” in ForEachMake model conform to Identifiable or provide id: parameter
Keyboard covers text fieldWrap in ScrollView or use .scrollDismissesKeyboard(.interactively)
Sheet not dismissingUse @Environment(\.dismiss) var dismiss and call dismiss()
Animation not workingEnsure value parameter matches in .animation(_:value:)