My notes from Lecture 2 covering @State, layout components, Swift type system, and building a memory card game with SwiftUI
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.
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)>>.
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.
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")
}
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 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: Creates constants that never changevar: For values that will varyBest practice: Start with let and change to var only when the compiler complains.
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
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 supports three main constructs:
if statements and switchlet 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)
}
}
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.
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.
print() output in Xcode 14.3+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.