Jeffrey Hicks

Jeffrey Hicks

Platform Eng @R360

Stanford CS193p 2023: Lecture 02 Notes

My notes from Lecture 2 covering @State, layout components, Swift type system, and building a memory card game with SwiftUI

By Stanford CS193p • Sep 1, 2025
Lecture 2 | Stanford CS193p 2023
by Stanford CS193p
Published Aug 25, 2023

Screen capture of Lecture 2 of Stanford's CS193p course (Developing iOS Applications using SwiftUI) from Spring quarter 2023. More SwiftUI basics.

This lecture covers essential SwiftUI concepts and Swift programming techniques through building a memory card game. Below are the key concepts and techniques demonstrated.

Core SwiftUI Principles

Views and Immutability

Views are immutable structures - this is a fundamental principle of SwiftUI. When you create a ContentView: View, you’re declaring that ContentView behaves like a View through protocol conformance, not inheritance.

struct ContentView: View {
    var body: some View {
        // View content here
    }
}

The some View return type uses type inference to let the compiler determine the actual View type being returned (like Text, VStack, or complex nested types). This eliminates the need to specify complex return types like VStack<TupleView<(Text, Text)>>.

@State for Temporary UI State

Since Views are immutable, you need @State for temporary state that can change within a View:

struct CardView: View {
    @State var isFaceUp = false
    
    var body: some View {
        // Can modify isFaceUp with @State
        Button("Flip") {
            isFaceUp.toggle() // Bool has a toggle() function
        }
    }
}

Important: @State is only for temporary display state, not for core application data. The @State property wrapper creates a pointer to memory where the actual value is stored, allowing the underlying value to change while keeping the View immutable.

SwiftUI Layout Components

Stack Views

VStack, HStack, and ZStack are the primary layout containers:

VStack {
    Text("Top")
    Text("Bottom")
}

HStack {
    Text("Left")
    Spacer() // Fills available space
    Text("Right")
}

ZStack(alignment: .top) { // Can specify alignment
    RoundedRectangle(cornerRadius: 10)
    Text("Overlay")
}

ForEach for Dynamic Content

Since ViewBuilder doesn’t support traditional for loops, use ForEach:

let emojis = ["🎃", "👻", "🕷️", "😈"]

HStack {
    ForEach(0..<4, id: \.self) { index in
        CardView(content: emojis[index])
    }
}

The id: \.self parameter is required for ranges (explanation deferred to future lectures).

Swift Language Features

Type Inference and Strong Typing

Swift is extremely strongly typed but uses extensive type inference to reduce verbosity:

let base = RoundedRectangle(cornerRadius: 10) // Type inferred
// Instead of: let base: RoundedRectangle = RoundedRectangle(cornerRadius: 10)

Use Option+click on variables to see their inferred types.

let vs var

  • let: Creates constants that never change
  • var: For values that will vary

Best practice: Start with let and change to var only when the compiler complains.

Arrays and Generics

Arrays use generic syntax to specify the element type:

let emojis: [String] = ["🎃", "👻", "🕷️"] // Preferred syntax
let emojis: Array<String> = ["🎃", "👻", "🕷️"] // Also valid

// Indexing
let firstEmoji = emojis[0] // Array bounds checking - crashes if out of range

Trailing Closure Syntax

When the last parameter is a function, you can use trailing closure syntax:

// Standard syntax
ZStack(alignment: .center, content: {
    // Views here
})

// Trailing closure
ZStack(alignment: .center) {
    // Views here
}

// With default alignment, can omit parentheses entirely
ZStack {
    // Views here
}

ViewBuilder Capabilities

ViewBuilder supports three main constructs:

  1. Conditionals: if statements and switch
  2. Lists: Multiple Views in sequence
  3. Local variables: let declarations only (no mutation like x = x + 1)
var body: some View {
    let base = RoundedRectangle(cornerRadius: 10) // ✅ Allowed
    
    VStack {
        if someCondition {
            Text("Conditional content")
        }
        base.fill(.blue)
        base.stroke(.red)
    }
}

Gesture Handling

Add tap gestures using the onTapGesture view modifier:

ZStack {
    // Card content
}
.onTapGesture {
    // This is normal Swift code, not ViewBuilder
    print("Card tapped")
    isFaceUp.toggle()
}

The closure inside onTapGesture is normal Swift code where you can perform any operations.

Shape and Fill Concepts

Shapes like RoundedRectangle and Circle have default behaviors:

RoundedRectangle(cornerRadius: 10) // Fills by default
    .fill(.white) // Explicit fill with color
    .strokeBorder(.red) // Stroke the border

If you don’t specify .fill() or .stroke(), shapes will fill by default.

Development Tools Integration

Xcode Features

  • Live Previews: Can now handle tap gestures and print() output in Xcode 14.3+
  • Inspector Mode: Bidirectional selection between visual editor and code
  • Source Control: Built-in Git integration with diff viewing

Debugging

Use print() statements extensively for debugging declarative UI, especially in the first few weeks of learning SwiftUI. The declarative nature can make traditional step-through debugging more challenging than imperative code.

This lecture establishes the foundation for building interactive SwiftUI applications while emphasizing the importance of understanding Swift’s type system and SwiftUI’s declarative, immutable architecture.

Related

#swiftui