RMIT University

Swift & SwiftUI Cheatsheet

Your comprehensive quick reference for Swift and SwiftUI. This cheatsheet covers essential Swift language fundamentals (variables, control flow, collections) and key SwiftUI concepts including views, layouts, modifiers, state management, interactivity, and navigation.

Swift Cheatsheet

Comments

Comments are notes for developers and are ignored by the compiler. Use // for single-line comments and /* ... */ for multi-line comments.

// This is a single-line comment.
// It's useful for short explanations.

/*
 This is a multi-line comment.
 It can span several lines and is often
 used for longer descriptions or for temporarily
 disabling a block of code.
*/
let a = 5 // Comments can also be at the end of a line.

Output

Comments do not produce any visible output in your app but are crucial for code readability.

Variable & Constant

Use `var` for data that can change (like a user's score) and `let` for data that is set once and never changes (like a birth date).

Best Practice: Prefer `let` over `var`. This makes your code safer and easier to understand because it prevents accidental changes. Use `var` only when you explicitly need to modify the value later.

var score = 0  // Variable (mutable)
let pi = 3.14  // Constant (immutable)

print("Initial score: \(score)")
score = 10  // Can change because it's var
print("New score: \(score)")
print("Pi value: \(pi)")

Output

Initial score: 0
New score: 10
Pi value: 3.14

Basic Data Types

Swift needs to know the "type" of data you are storing. Common types include text (`String`), whole numbers (`Int`), decimals (`Double`), true/false (`Bool`), and collections like arrays (`[String]`, `[Int]`).

Note: Swift has powerful type inference, so you often don't need to write the type explicitly. var name = "Alex" works just fine!

var name: String = "Alex"
var age: Int = 16
var height: Double = 1.75
var isStudent: Bool = true

// Arrays
var teamNames: [String] = ["Warriors", "Lakers", "Bulls"]
var scores: [Int] = [98, 102, 85]

// Print the values to see the output
print("Name: \(name)")
print("Age: \(age)")
print("Height: \(height)m")
print("Is student: \(isStudent)")
print("Team names: \(teamNames)")
print("Scores: \(scores)")

Output

Name: Alex
Age: 16
Height: 1.75m
Is student: true
Team names: ["Warriors", "Lakers", "Bulls"]
Scores: [98, 102, 85]

String Interpolation

To mix text with variables or constants in Swift, use a backslash with parentheses: \(yourVariable). This works in strings for print statements and SwiftUI Text views.

let name = "Alex"
let age = 16
let score = 95

print("My name is \(name)")
print("I am \(age) years old")
print("\(name) scored \(score) points!")

Output

My name is Alex
I am 16 years old
Alex scored 95 points!

If/Else Statements

Use if, else if, and else to make decisions in your code based on certain conditions.

let temperature = 25
if temperature > 30 {
    print("It's a hot day!")
} else if temperature < 15 {
    print("It's a cold day.")
} else {
    print("The weather is mild.")
}

Output

The weather is mild.

For Loops

for-in loops allow you to repeat a block of code for each item in a sequence, like an array or a range of numbers.

// Looping through an array
let names = ["Anna", "Alex", "Brian"]
for name in names {
    print("Hello, \(name)!")
}

// Looping through a range of numbers
for number in 1...3 {
    print("Number is \(number)")
}

Output

Hello, Anna!
Hello, Alex!
Hello, Brian!
Number is 1
Number is 2
Number is 3

Random Numbers & Elements

Swift makes it easy to get random values, whether from a numerical range or from a collection like an array. Use .randomElement() ?? defaultValue to safely pick from an array.

// Random number in a range (e.g., 1 to 100)
let randomNumber = Int.random(in: 1...100)
print("Random number: \(randomNumber)")

// Random element from an array
let fruits = ["Apple", "Banana", "Cherry"]
let randomFruit = fruits.randomElement() ?? "No fruit!"
print("Random fruit: \(randomFruit)")

let emptyArray: [String] = []
let randomFromEmpty = emptyArray.randomElement() ?? "Default Value"
print("From empty: \(randomFromEmpty)")

Example Output

Random number: [some number 1-100]
Random fruit: Apple (or Banana, Cherry)
From empty: Default Value

Functions

Functions are reusable blocks of code that perform a specific task. They can take inputs (parameters) and produce an output (return value).

Note: When calling a function, you use an argument label (like `a:` and `b:` below). Inside the function, you use the parameter name (like `a` and `b`).

// Function with no parameters and no return value
func sayHello() {
    print("Hello, World!")
}
sayHello() // Call the function

// Function with parameters and a return value
func add(a: Int, b: Int) -> Int {
    return a + b
}
let sum = add(a: 5, b: 3)
print("Sum: \(sum)")

Output

Hello, World!
Sum: 8

Switch Statements

A `switch` statement considers a value and compares it against several possible matching patterns. It's a powerful alternative to long `if-else if` chains.

let dayOfWeek = 4
var dayName: String
switch dayOfWeek {
case 1:
    dayName = "Sunday"
case 2:
    dayName = "Monday"
case 3:
    dayName = "Tuesday"
case 4:
    dayName = "Wednesday"
default:
    dayName = "Other day"
}
print("Day \(dayOfWeek) is \(dayName).")

Output

Day 4 is Wednesday.

Dictionaries

A dictionary stores associations between keys of the same type and values of the same type in a collection with no defined ordering.

// Creating and accessing a dictionary
var occupations = [
    "Malcolm": "Captain",
    "Kaylee": "Mechanic",
]
occupations["Jayne"] = "Public Relations"

print(occupations["Kaylee"] ?? "Unknown")

// Iterating over a dictionary
for (person, job) in occupations {
    print("\(person)'s job is \(job).")
}

Output

Mechanic
Malcolm's job is Captain.
Jayne's job is Public Relations.
Kaylee's job is Mechanic.

Optionals

Optionals handle the absence of a value. An optional says either "there is a value, and it equals x" or "there isn’t a value at all" (`nil`). This prevents crashes from using `nil` values.

Recommendation: Use optional binding (`if let`) to safely unwrap and use a value. Use the nil-coalescing operator (`??`) to provide a default value if the optional is `nil`.

var optionalName: String? = "John Appleseed"
// optionalName = nil // It can also be nil

// 1. Optional Binding (Safest)
if let name = optionalName {
    print("Hello, \(name)")
} else {
    print("Hello, guest!")
}

// 2. Nil-Coalescing Operator (Provides a default)
let greeting = "Hello, \(optionalName ?? "guest")"
print(greeting)

Output

Hello, John Appleseed
Hello, John Appleseed

Structs vs. Classes

Both are blueprints for objects, but they have a key difference: `Struct`s are value types (copies are made), while `Class`es are reference types (pointers to a shared instance).

Recommendation: Start with `struct`s for your data models. Use a `class` only when you need features like inheritance or when multiple parts of your app must share and modify a single instance of that data.

// Struct (Value Type) - a copy is made
struct User { var name: String }
var user1 = User(name: "Tom")
var user2 = user1
user2.name = "Jerry"
// user1 is unchanged
print("User1: \(user1.name), User2: \(user2.name)")

// Class (Reference Type) - both point to the same object
class Game { var score = 0 }
var game1 = Game()
var game2 = game1
game2.score = 50
// game1 is also changed
print("Game1 Score: \(game1.score), Game2 Score: \(game2.score)")

Output

User1: Tom, User2: Jerry
Game1 Score: 50, Game2 Score: 50

Closures

Closures are self-contained blocks of functionality that can be passed around and used in your code. They can capture and store references to any constants and variables from the context in which they are defined.

let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]

// Trailing closure syntax
let sortedNames = names.sorted { s1, s2 in
    s1 < s2
}
print(sortedNames)

// Shorthand argument names
let reversedNames = names.sorted(by: { $0 > $1 })
print(reversedNames)

Output

["Alex", "Barry", "Chris", "Daniella", "Ewa"]
["Ewa", "Daniella", "Chris", "Barry", "Alex"]

Codable

The `Codable` protocol is a type alias for the `Encodable` and `Decodable` protocols. It's used to easily encode and decode custom data types to and from external representations like JSON.

struct Landmark: Codable {
    var name: String
    var foundingYear: Int
}

let jsonString = """
{
    "name": "Eiffel Tower",
    "foundingYear": 1887
}
"""
let jsonData = Data(jsonString.utf8)

let decoder = JSONDecoder()
if let landmark = try? decoder.decode(Landmark.self, from: jsonData) {
    print("\(landmark.name) was founded in \(landmark.foundingYear).")
}

Output

Eiffel Tower was founded in 1887.

@AppStorage

A property wrapper that reads and writes to `UserDefaults`. It's a convenient way to persist small pieces of data, like user settings, across app launches.

// In a SwiftUI View
struct SettingsView: View {
    @AppStorage("username") var username: String = "Anonymous"

    var body: some View {
        VStack {
            Text("Welcome, \(username)!")
            Button("Log in") {
                username = "Taylor"
            }
        }
    }
}

Output

The view will initially show "Welcome, Anonymous!". After tapping the button, it will change to "Welcome, Taylor!" and this will be saved for the next app launch.

readLine()

Reads a line of text from the standard input, which is useful for command-line tools. It returns an optional `String`, so you need to unwrap it safely.

print("What is your name?")
if let name = readLine() {
    print("Hello, \(name)!")
} else {
    print("No input provided.")
}

Example Interaction

What is your name?
User types: Jerry
Hello, Jerry!

SwiftUI Cheatsheet

Layout

VStack

Use `VStack` to arrange views in a vertical line, one on top of the other, like stacking blocks.

VStack(spacing: 10) {
    Text("Item 1")
    Text("Item 2")
}

Output

Item 1
Item 2

HStack

Stacks views horizontally. Good for icons next to text.

HStack(spacing: 8) {
    Image(systemName: "star.fill")
    Text("My Hobby")
}

Output

My Hobby

Spacer

A `Spacer` is a flexible, invisible view that pushes other views as far apart as possible. It's great for aligning things to the edges.

HStack {
    Text("Start")
    Spacer()
    Text("End")
}

Output

Start
End

ZStack

Layers views on top of each other. Useful for backgrounds.

ZStack {
    Color.purple.ignoresSafeArea() // Background
    Text("Content on Top") // Foreground
        .foregroundColor(.white)
}

Output

Content on Top

Combining Layouts

VStack, HStack, ZStack, and Spacer can be nested and combined to create complex user interfaces. This example shows a card-like structure.

ZStack(alignment: .topLeading) {
    // Background for the card
    Color.gray.opacity(0.1)
        .frame(width: 250, height: 150)
        .cornerRadius(10)

    VStack(alignment: .leading, spacing: 8) {
        HStack {
            Image(systemName: "star.fill")
                .foregroundColor(.yellow)
            Text("Featured Item")
                .font(.headline)
            Spacer() // Pushes content left
        }
        Text("This item uses ZStack, VStack, HStack, and Spacer.")
            .font(.caption)
    }
    .padding() // Padding inside the VStack
}
.frame(width: 250, height: 150) // Frame for ZStack

Output

Featured Item

This item uses ZStack, VStack, HStack, and Spacer.

ScrollView

A scrollable view. You can specify the scroll direction (`.vertical` or `.horizontal`). It's great for content that doesn't fit on one screen.

ScrollView {
    VStack(alignment: .leading, spacing: 10) {
        ForEach(0..50) { index in
            Text("Row \(index)")
        }
    }
}
.frame(height: 200) // Give the ScrollView a fixed height

Output

Row 0

Row 1

Row 2

...

Row 49

Views & Modifiers

Text("Hello")

Displays text on the screen.

Text("Your Name")
    .font(.title)

Output

Your Name

Image("imageName")

Displays an image from your project's "Assets" folder.

Image("avatar1") // "avatar1" must be in Assets
    .resizable()
    .frame(width: 100, height: 100)

Output

avatar1
100x100

Image(systemName: "...")

Displays an icon from Apple's SF Symbols library. These are vector-based and adapt well to different sizes and styles.

// Displaying various SF Symbols
VStack(spacing: 15) {
    Image(systemName: "star.fill")
    Image(systemName: "heart.circle.fill")
    Image(systemName: "trash.slash.fill")
    Image(systemName: "folder.badge.plus")
}
.font(.largeTitle) // Apply a consistent size
.foregroundColor(.blue) // Apply a consistent color

Output

AsyncImage

Loads and displays an image from a URL asynchronously. It shows a placeholder while the image is loading.

AsyncImage(url: URL(string: "https://picsum.photos/id/237/200/300")) { image in
    image.resizable().scaledToFit()
} placeholder: {
    ProgressView() // Shows a loading spinner
}
.frame(width: 100, height: 100)

Output

AsyncImage example

(Image loaded from URL)

List { ... }

Creates a scrolling list of items. Often contains HStacks.

List {
    HStack {
        Image(systemName: "music.note")
        Text("Music")
    }
    HStack {
        Image(systemName: "film.fill")
        Text("Movies")
    }
}
.frame(height: 100) // Give the list a size
.listStyle(.plain)

Output (Simplified)

Music
Movies

ProgressView

Displays a view that shows the progress of a task over time. It can be indeterminate (a spinner) or determinate (a progress bar).

VStack(spacing: 20) {
    // Indeterminate (spinner)
    ProgressView()

    // Determinate (progress bar)
    ProgressView(value: 0.75) {
        Text("Loading...")
    }
}

Output

Loading...

Loading...

Advanced Views & Controls

Shapes (Circle, Rectangle)

SwiftUI provides several built-in shapes that you can use as views. They can be styled with modifiers like `.fill`, `.stroke`, and `.frame`.

HStack(spacing: 20) {
    Circle()
        .fill(Color.blue)
        .frame(width: 80, height: 80)

    Rectangle()
        .stroke(Color.red, lineWidth: 5)
        .frame(width: 120, height: 80)
}

Output

MapKit Integration

Display an interactive map by importing `MapKit` and using the `Map` view. You can specify a coordinate region to display.

import MapKit

struct MapView: View {
    @State private var region = MKCoordinateRegion(
        center: CLLocationCoordinate2D(latitude: 10.729, longitude: 106.694),
        span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01)
    )

    var body: some View {
        Map(coordinateRegion: $region)
            .frame(height: 200)
    }
}

Output

Map View

(Simulated Map View)

Data Flow & Architecture

MVVM Design Pattern

The Model-View-ViewModel (MVVM) pattern separates your code into three parts: the Model (data), the View (UI), and the ViewModel (business logic), making your code cleaner and easier to manage.

// Model
struct Counter { var value = 0 }

// ViewModel
class CounterViewModel: ObservableObject {
    @Published private(set) var counter = Counter()
    func increment() { counter.value += 1 }
}

// View
struct CounterView: View {
    @StateObject private var viewModel = CounterViewModel()
    var body: some View {
        VStack {
            Text("Count: \(viewModel.counter.value)")
            Button("Increment") { viewModel.increment() }
        }
    }
}

Observation Framework (@Observable)

Introduced in iOS 17, the `@Observable` macro simplifies state management by automatically making class properties observable, removing the need for `@Published`.

import Observation

@Observable
class Account {
    var balance: Double = 100.0
}

struct AccountView: View {
    @State private var account = Account()
    var body: some View {
        Text("Balance: $\(account.balance)")
    }
}

SwiftData

SwiftData is a powerful and expressive framework for data persistence. Use the `@Model` macro to turn a Swift class into a stored model, and `@Query` to fetch data.

import SwiftData

@Model
class DataItem {
    var name: String
    init(name: String) { self.name = name }
}

struct DataListView: View {
    @Query var items: [DataItem]
    var body: some View {
        List(items) { item in
            Text(item.name)
        }
    }
}

CoreData

CoreData is a mature and robust framework for managing a model layer. It can persist data locally on the device. It requires more setup than SwiftData but is powerful for complex data models.

// In DataController
let container = NSPersistentContainer(name: "Model")

// In a SwiftUI View
@Environment(\.managedObjectContext) var moc
@FetchRequest(sortDescriptors: []) var items: FetchedResults

Button("Add Item") {
    let newItem = Item(context: moc)
    newItem.name = "New Item"
    try? moc.save()
}

@Binding

Creates a two-way connection between a property that stores data, and a view that displays and changes it. It allows a child view to modify a `@State` property owned by its parent.

struct ParentView: View {
    @State private var isToggleOn = false
    var body: some View { ChildView(isToggleOn: $isToggleOn) }
}

struct ChildView: View {
    @Binding var isToggleOn: Bool
    var body: some View { Toggle("Is On", isOn: $isToggleOn) }
}

@StateObject vs. @ObservedObject

Both are used for complex reference-type data (classes). Use `@StateObject` when a view creates and owns the object. Use `@ObservedObject` when a view receives the object from a parent view.

class UserProgress: ObservableObject {
    @Published var score = 0
}

struct GameView: View {
    // GameView creates and owns the progress object
    @StateObject private var progress = UserProgress()

    var body: some View {
        VStack {
            Text("Score: \(progress.score)")
            // Pass the object to a child view
            ScoreButton(progress: progress)
        }
    }
}

struct ScoreButton: View {
    // ScoreButton receives and observes the object
    @ObservedObject var progress: UserProgress

    var body: some View {
        Button("Increase Score") {
            progress.score += 10
        }
    }
}

@EnvironmentObject

Injects an object into the environment, making it accessible to any child view in the hierarchy without passing it down manually. Useful for shared data like user settings or themes.

// Inject the object in the root view of the app
// SceneDelegate.swift or App.swift
let settings = UserSettings()
ContentView().environmentObject(settings)

// Any child view can now access it
struct SomeChildView: View {
    @EnvironmentObject var settings: UserSettings

    var body: some View {
        Text("Username: \(settings.username)")
    }
}

Firebase Authentication

Integrate secure authentication into your app using Firebase. It supports email & password, phone, and social logins like Google and Facebook.

import Firebase

// Sign Up a new user
Auth.auth().createUser(withEmail: email, password: password)

// Sign In an existing user
Auth.auth().signIn(withEmail: email, password: password)

Functionality

Provides user authentication services, managed in the Firebase console.

Cloud Firestore

A flexible, scalable NoSQL cloud database to store and sync data for client- and server-side development. Data is synced in real-time across all clients.

import FirebaseFirestore

let db = Firestore.firestore()

// Add a new document to a collection
db.collection("users").addDocument(data: ["name": "Ada"])

// Read documents from a collection
db.collection("users").getDocuments() { (querySnapshot, err) in
    // ... loop through documents
}

Functionality

A real-time, cloud-hosted database.

Common Modifiers (Your Superpowers!)

Modifier Order Matters

The order in which you apply modifiers is important. A modifier usually wraps the view and returns a new, modified view. Applying them in a different order can produce a different result.

// Example 1: Padding then Background
Text("Hello")
    .padding()
    .background(Color.red)

// Example 2: Background then Padding
Text("Hello")
    .background(Color.red)
    .padding()

Output

Hello
Hello

Custom Fonts

To use custom fonts, add the font file (e.g., `.ttf` or `.otf`) to your project, register it in your `Info.plist` file, and then use it with the `.font(.custom(...))` modifier.

// 1. Add font file to project
// 2. Add "Fonts provided by application" key to Info.plist
// 3. Use in your view
Text("Custom Font!")
    .font(.custom("YourFontName-Regular", size: 24))

ViewModifier

Create reusable sets of modifiers by defining a custom `ViewModifier`. This helps keep your code clean and consistent, especially for styles you apply often, like for buttons or titles.

struct PrimaryTitle: ViewModifier {
    func body(content: Content) -> some View {
        content
            .font(.largeTitle)
            .foregroundColor(.blue)
            .padding()
            .background(Color.gray.opacity(0.2))
            .cornerRadius(10)
    }
}

// Usage
Text("Main Title")
    .modifier(PrimaryTitle())

.font()

Changes text font and size. E.g.,
.font(.title),
.font(.headline),
.font(.system(size: 32, weight: .bold)),
.font(.custom("Corinthia-Regular", size: 70)),
.font(.custom("Silkscreen-Regular", size: 40)),
.font(.custom("Sacramento-Regular", size: 50)).

.foregroundColor()

Changes text or icon color. E.g.,
.foregroundColor(.blue),
.foregroundColor(Color.white).

.background()

Puts a colored background. E.g.,
.background(Color.black.opacity(0.5)).

.padding()

Adds space around a view. E.g.,
.padding() (all sides),
.padding(.horizontal) (left & right),
.padding([.top, .leading], 10) (specific sides & amount).

.cornerRadius()

Rounds the corners. E.g.,
.cornerRadius(15).

.frame()

Gives a view a fixed size. E.g.,
.frame(width: 150, height: 150),
.frame(height: 180).

.resizable()

(For Images) Allows the image to be resized.

.aspectRatio() / .scaledToFill()

(For Images) Controls how an image fills its frame. E.g.,
.aspectRatio(contentMode: .fill).

.clipShape()

(For Images) Clips the view into a shape. E.g.,
.clipShape(Circle()).

.overlay()

Layers another view on top, often for borders. E.g.,
.overlay(Circle().stroke(Color.white, lineWidth: 4)).

.shadow()

Adds a drop shadow. E.g.,
.shadow(radius: 10).

.offset()

Moves a view from its original position without affecting the layout of other views. E.g.,
.offset(x: 20, y: -10) moves the view 20 points right and 10 points up.

.position()

Places the center of a view at a specific coordinate within its parent's coordinate space. E.g.,
.position(x: 100, y: 150).

For dynamic centering, use GeometryReader to get the parent's size:

GeometryReader { geometry in
    Text("Centered")
        .position(x: geometry.size.width / 2, y: geometry.size.height / 2)
}

.listStyle()

(For Lists) Changes list appearance. E.g.,
.listStyle(.plain).

.ignoresSafeArea()

(For Colors/Backgrounds) Makes it fill the whole screen, even under notches. E.g.,
Color.black.ignoresSafeArea().

Modifier Combo: Styled Button Text

Combining modifiers to create a common button style with padding, background, corner radius, and text styling.

Text("Tap Me!")
    .font(.headline)
    .foregroundColor(.white)
    .padding() // Add padding around the text
    .background(Color.blue) // Blue background
    .cornerRadius(10) // Rounded corners
    .shadow(radius: 5) // Subtle shadow

Output

Tap Me!

Modifier Combo: Circular Profile Image

Common pattern for profile images: resizable, specific frame, circular clip, and an overlay border.

Image("avatar1") // Assuming "avatar1" is in Assets
    .resizable()
    .scaledToFill() // Fill the frame, may crop
    .frame(width: 100, height: 100)
    .clipShape(Circle()) // Clip to a circle
    .overlay( // Add a border
        Circle().stroke(Color.orange, lineWidth: 4)
    )
    .shadow(radius: 7)

Output

avatar1
Circular

Modifier Combo: Card View

Creating a card-like appearance using padding, background, corner radius, and shadow on a VStack.

VStack(alignment: .leading) {
    Text("Card Title")
        .font(.title2).bold()
        .foregroundColor(.primary) // Use primary text color
    Text("This is some descriptive text for the card content. It explains what the card is about.")
        .font(.body)
        .foregroundColor(.secondary) // Use secondary text color for subtlety
}
.padding() // Inner padding for content
.background(Color.gray.opacity(0.1)) // Light gray background, slightly transparent
.cornerRadius(12) // Rounded corners for the card
.shadow(radius: 5) // Default shadow with a radius
.padding() // Outer padding to see shadow

Output

Card Title

This is some descriptive text for the card content. It explains what the card is about.

Visual & Transformational Effects

.opacity()

Controls the transparency of a view. A value of 1.0 is fully opaque, while 0.0 is fully transparent.

Text("Faded Text")
    .opacity(0.5)

Output

Faded Text

.rotationEffect()

Rotates a view by a specified angle. You can specify the angle in degrees or radians.

Text("Rotated")
    .rotationEffect(.degrees(-45))

Output

Rotated

.scaleEffect()

Scales a view larger or smaller. A value of 1.0 is the original size.

Text("Bigger")
    .scaleEffect(1.5)

Output

Bigger

.blur()

Applies a Gaussian blur effect to a view. The radius determines the intensity of the blur.

Text("Blurred")
    .blur(radius: 3.0)

Output

Blurred

Gradients

Create smooth transitions between colors. Common types include `LinearGradient`, `RadialGradient`, and `AngularGradient`.

VStack(spacing: 10) {
    LinearGradient(colors: [.blue, .white], startPoint: .top, endPoint: .bottom)
    RadialGradient(colors: [.yellow, .red], center: .center, startRadius: 5, endRadius: 50)
    AngularGradient(colors: [.green, .blue], center: .center)
}
.frame(height: 150)

Output

Linear
Radial
Angular

Interactivity & Animation

State & Actions

Use `@State` to store a value that can change. A `Button` can change that value, and SwiftUI will automatically update the view.

@State private var tapCount = 0

VStack(spacing: 15) {
    Text("Taps: \(tapCount)")
    Button("Tap Me") {
        tapCount += 1
    }
}

Interactive Output

Taps: 0
Tap Me

.onTapGesture { ... }

Makes any view tappable. Code inside the curly braces runs on tap.

// Add to an Image or Text view
Image("avatar1")
    .onTapGesture {
        // Action: what happens when tapped
        currentQuote = quotes.randomElement() ?? "Hmm..."
    }

Animation

SwiftUI provides two main ways to create animations: implicit animations using the `.animation()` modifier, and explicit animations using the `withAnimation` block.

@State private var isRotated = false

// Implicit Animation
Image(systemName: "arrow.up")
    .rotationEffect(isRotated ? .degrees(180) : .zero)
    .animation(.easeInOut, value: isRotated)

// Explicit Animation
Button("Rotate") {
    withAnimation {
        isRotated.toggle()
    }
}

.onAppear() / .onDisappear()

Triggers an action when a view becomes visible or is removed from the view hierarchy. Useful for loading data or cleanup.

Text("My View")
    .onAppear {
        print("View appeared!")
    }
    .onDisappear {
        print("View disappeared!")
    }

Output

(Actions print to the console)

.sheet() & .presentationDetents()

Presents a view modally. Use `.presentationDetents()` to specify the height of the sheet, such as half-screen (`.medium`) or full-screen (`.large`).

@State private var isShowingSheet = false

Button("Show Sheet") {
    isShowingSheet = true
}
.sheet(isPresented: $isShowingSheet) {
    MySheetView()
        .presentationDetents([.medium, .large])
}

Output

(Presents a resizable sheet from the bottom)

.alert()

Displays an alert message to the user, also controlled by a boolean @State variable.

@State private var isShowingAlert = false

Button("Show Alert") {
    isShowingAlert = true
}
.alert("Important Message", isPresented: $isShowingAlert) {
    Button("OK", role: .cancel) { }
}

Output

(Shows a system alert pop-up)

Navigation

NavigationView & NavigationLink

`NavigationView` creates a navigation bar. `NavigationLink` is a special button that pushes a new view onto the screen.

Note: `NavigationView` is deprecated in iOS 16+. It's recommended to use `NavigationStack` for new projects for more robust navigation control.

NavigationView {
    VStack {
        NavigationLink(destination: Text("Detail View")) {
            Text("Go to Detail")
        }
    }
    .navigationTitle("Master View")
}

Output: Master → Detail View

Go to Detail
Detail Content

NavigationStack

The modern way to handle navigation, introduced in iOS 16. It provides a more flexible and programmatic way to manage a stack of views.

NavigationStack {
    List(destinations) { destination in
        NavigationLink(destination.name, value: destination)
    }
    .navigationDestination(for: Destination.self) { destination in
        DetailView(destination: destination)
    }
    .navigationTitle("Destinations")
}

Modal Presentation with .sheet()

Presents a view modally over the current one. This is a common way to handle self-contained tasks like logging in, creating a new item, or showing settings, without adding to the main navigation stack.

@State private var isShowingLoginSheet = false

VStack {
    Button("Log In") {
        isShowingLoginSheet = true
    }
}
.sheet(isPresented: $isShowingLoginSheet) {
    // The view to present modally
    LoginView()
}

Output

(Tapping the button presents the `LoginView` from the bottom of the screen)