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. Modern SwiftUI examples target iOS 17 and later; older compatibility patterns are clearly labeled.

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. Shorthand optional binding (Swift 5.7+)
if let optionalName {
    print("Hello, \(optionalName)")
} 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: Structs are value types (copies are made), while Classes are reference types (pointers to a shared instance).

Recommendation: Start with structs 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. Codable belongs to Swift’s standard library; this JSON example also uses Foundation.

import Foundation

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.

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
        .foregroundStyle(.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)
        .clipShape(.rect(cornerRadius: 10))

    VStack(alignment: .leading, spacing: 8) {
        HStack {
            Image(systemName: "star.fill")
                .foregroundStyle(.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
.foregroundStyle(.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

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. For iOS 17 and later, use Observation for view-model state.

import SwiftUI
import Observation

// ViewModel
@Observable
final class CounterViewModel {
    private(set) var count = 0
    func increment() { count += 1 }
}

// View
struct CounterView: View {
    @State private var viewModel = CounterViewModel()

    var body: some View {
        VStack {
            Text("Count: \(viewModel.count)")
            Button("Increment") { viewModel.increment() }
        }
    }
}

Observation Framework (@Observable)

On iOS 17 and later, @Observable is the preferred model-observation approach. Store an owned model with @State, pass it normally, and use @Bindable only when a child needs bindings to its properties.

import SwiftUI
import Observation

@Observable
final class Account {
    var owner = ""
}

struct AccountView: View {
    @State private var account = Account()

    var body: some View {
        AccountEditor(account: account)
    }
}

struct AccountEditor: View {
    @Bindable var account: Account

    var body: some View {
        TextField("Owner", text: $account.owner)
    }
}

@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) }
}

@AppStorage

A SwiftUI 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.

Legacy: @StateObject & @ObservedObject

These wrappers remain valid for models that use the older ObservableObject protocol. Use @StateObject for an object a view owns and @ObservedObject for one it receives. For new iOS 17+ code, prefer @Observable, @State, and ordinary stored properties.

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
        }
    }
}

@Environment with @Observable

For modern shared reference data, put an @Observable model into the environment with .environment(model) and retrieve it by type with @Environment(Model.self).

import SwiftUI
import Observation

@Observable
final class UserSettings {
    var username = "Anonymous"
}

@main
struct ExampleApp: App {
    @State private var settings = UserSettings()

    var body: some Scene {
        WindowGroup {
            SomeChildView()
                .environment(settings)
        }
    }
}

struct SomeChildView: View {
    @Environment(UserSettings.self) private var settings

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

Framework Integrations

MapKit for SwiftUI

MapKit is a separate Apple framework with SwiftUI integration. On iOS 17 and later, control the map with MapCameraPosition and add content such as Marker using the map content builder.

import SwiftUI
import MapKit

struct MapView: View {
    private let campus = CLLocationCoordinate2D(
        latitude: 10.729,
        longitude: 106.694
    )

    @State private var position: MapCameraPosition = .region(
        MKCoordinateRegion(
            center: CLLocationCoordinate2D(latitude: 10.729, longitude: 106.694),
            span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01)
        )
    )

    var body: some View {
        Map(position: $position) {
            Marker("RMIT Vietnam", coordinate: campus)
        }
        .frame(height: 200)
    }
}

Output

Map View

(Simulated Map View)

SwiftData

SwiftData is a separate persistence framework that integrates with SwiftUI. Use @Model for persisted model types, @Query in views, and attach a model container to the app or view hierarchy.

import SwiftUI
import SwiftData

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

struct DataListView: View {
    @Query(sort: \DataItem.name) private var items: [DataItem]

    var body: some View {
        List(items) { item in
            Text(item.name)
        }
    }
}

// Add at the app or root-view level:
DataListView()
    .modelContainer(for: DataItem.self)

Core Data

Core Data is a separate model and persistence framework. It remains supported and is common in existing apps; SwiftData is generally the more concise choice for new Swift-first apps.

import SwiftUI
import CoreData

// In a SwiftUI view whose ancestor injects a managed object context
@Environment(\.managedObjectContext) private var context
@FetchRequest(sortDescriptors: [])
private var items: FetchedResults<Item>

Button("Add Item") {
    let newItem = Item(context: context)
    newItem.name = "New Item"

    do {
        try context.save()
    } catch {
        print("Save failed: \(error)")
    }
}

Firebase Authentication

Firebase Authentication is a third-party service, not SwiftUI. Import its product module and use its async API from an asynchronous context such as a Task.

import FirebaseAuth

func signIn(email: String, password: String) async {
    do {
        let result = try await Auth.auth().signIn(
            withEmail: email,
            password: password
        )
        print("Signed in: \(result.user.uid)")
    } catch {
        print("Sign-in failed: \(error)")
    }
}

Cloud Firestore

Cloud Firestore is a third-party database. Its current Swift APIs support structured concurrency with async/await.

import FirebaseFirestore

let database = Firestore.firestore()

func saveAndLoadUsers() async {
    do {
        try await database.collection("users").addDocument(
            data: ["name": "Ada"]
        )

        let snapshot = try await database.collection("users").getDocuments()
        for document in snapshot.documents {
            print(document.data())
        }
    } catch {
        print("Firestore error: \(error)")
    }
}

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 PrimaryTitleModifier: ViewModifier {
    func body(content: Content) -> some View {
        content
            .font(.largeTitle)
            .foregroundStyle(.blue)
            .padding()
            .background(Color.gray.opacity(0.2))
            .clipShape(.rect(cornerRadius: 10))
    }
}

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

.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)).

.foregroundStyle()

Changes text or icon color. E.g.,
.foregroundStyle(.blue),
.foregroundStyle(Color.white).
(Replaces the deprecated .foregroundColor(...) you may see in older tutorials.)

.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).

.clipShape()

Rounds the corners. E.g.,
.clipShape(.rect(cornerRadius: 15)),
.clipShape(Circle()).
(Replaces the deprecated .cornerRadius(15) you may see in older tutorials.)

.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)
    .foregroundStyle(.white)
    .padding() // Add padding around the text
    .background(Color.blue) // Blue background
    .clipShape(.rect(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()
        .foregroundStyle(.primary) // Use primary text color
    Text("This is some descriptive text for the card content. It explains what the card is about.")
        .font(.body)
        .foregroundStyle(.secondary) // Use secondary text color for subtlety
}
.padding() // Inner padding for content
.background(Color.gray.opacity(0.1)) // Light gray background, slightly transparent
.clipShape(.rect(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

.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()
    }
}

.task() / .onAppear() / .onDisappear()

Use .task for asynchronous work tied to a view’s lifetime. Use .onAppear for synchronous setup and .onDisappear for cleanup. SwiftUI automatically cancels the task when the view disappears.

Text("My View")
    .task {
        await viewModel.load()
    }
    .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

NavigationStack & NavigationLink

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

Note: older tutorials use NavigationView, which is deprecated since iOS 16. NavigationStack is the replacement and gives you more robust navigation control.

NavigationStack {
    VStack {
        NavigationLink("Go to Detail") {
            Text("Detail View")
        }
    }
    .navigationTitle("Master View")
}

Output: Master → Detail View

Go to Detail
→
Detail Content

Data-Driven Navigation

Use value-based links with .navigationDestination(for:) when navigation state needs to be programmatic, restorable, or deep-linkable.

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)