One. Course Details
This is the sixteenth and final lecture of Stanford University's CS193p iOS Application Development course for Spring 2025, taught by Paul Hegarty. The lecture covers three practical topics designed to help students with their final projects: implementing custom SwiftUI shapes, handling multi-touch gestures, and alternative persistence methods using the filesystem and JSON.
The lecture alternates between conceptual explanations and live coding demonstrations. It concludes with advice for final projects and marks the end of the formal lecture series, with only final project presentations remaining.Two. Key Learning Takeaways
Custom Shape implementation: Conform to the Shape protocol by implementing the path(in rect: CGRect) method to draw any custom 2D shape using Path drawing commands.
GeometryReader usage: A special view that provides access to its own size and coordinate system. It always takes maximum available space and can disrupt layout if not properly constrained.
SwiftUI gesture system: Attach gestures to views using the .gesture() modifier. Discrete gestures (tap, long press) use .onEnded(); continuous gestures (drag, rotate, magnify) use both .onChanged() and .onEnded().
iOS filesystem sandbox: All app data is stored in a secure sandbox. Access standard directories using URL static properties like .documentsDirectory and .applicationSupportDirectory.
Codable protocol: Converts Swift types to and from serialized formats like JSON. Swift can automatically synthesize conformance for structs and enums with all Codable properties.
JSON serialization: Use JSONEncoder and JSONDecoder to convert between Codable Swift types and JSON data for filesystem storage or network transmission.
UserDefaults: A simple key-value store for small amounts of non-critical data. Only supports property list types (String, Int, Double, Array, Dictionary, Data).
SwiftData and Codable interaction: Adding Codable conformance to a type allows SwiftData to automatically store it as a binary blob in the database.
Three. Course Gold Quotes
"Programming is like doing crossword puzzles. You can't get 26-across, you go to sleep, you wake up the next day and you know what it is." "Give your subconscious a chance to work on your final project by starting it early and working on it a little bit day by day." "Go do stuff I didn't do. Maps, Swift Charts, things like that. Go figure it out. That's how you learn." "Your app can only access the parts of this big Unix filesystem that's called your sandbox." "Codable is sometimes really easy. You just mark the struct you want Codable and you cross your fingers."Four. Layered Learning Notes
Module 1: Custom Shapes
Shape Protocol
-
All shapes conform to the
Shapeprotocol, which inherits fromView -
Required method:
func path(in rect: CGRect) -> Path -
Build a
Pathusing drawing commands:
swiftstruct Diamond: Shape { func path(in rect: CGRect) -> Path { Path { path in path.move(to: CGPoint(x: rect.midX, y: rect.minY)) path.addLine(to: CGPoint(x: rect.minX, y: rect.midY)) path.addLine(to: CGPoint(x: rect.midX, y: rect.maxY)) path.addLine(to: CGPoint(x: rect.maxX, y: rect.midY)) path.closeSubpath() } } } -
Shapes automatically support
.fill(),.stroke(), and all standard view modifiers
GeometryReader
-
A container view that provides its size and coordinate system to child views
-
Important behavior: Always takes the maximum available space (infinitely flexible)
-
Usage:
swiftGeometryReader { geometry in let rect = geometry.frame(in: .local) // Use rect to position or size child views } -
Coordinate systems:
-
.local: Relative to the GeometryReader itself -
.global: Relative to the entire screen
-
-
Common use cases:
-
Removing magic numbers from transitions
-
Creating responsive layouts that adapt to available space
-
Calculating positions for custom drawing
-
Module 2: Multi-Touch Gestures
Gesture Basics
-
Attach gestures to views with the
.gesture()modifier -
Two types of gestures:
-
Discrete: Tap, long press (happen all at once)
-
Continuous: Drag, rotate, magnify (update as fingers move)
-
Common Gestures
-
TapGesture:
swift.onTapGesture(count: 2) { // Handle double tap } -
RotationGesture:
swiftvar rotationGesture: some Gesture { RotationGesture() .onChanged { value in // value is the rotation angle in degrees/radians let index = Int(abs(value.degrees) / 90) % pegChoices.count currentPeg = pegChoices[index] } } -
MagnifyGesture (Pinch):
swiftvar magnifyGesture: some Gesture { MagnifyGesture() .onChanged { value in // value.magnification is the scale factor (1.0 = original) dynamicScale = value.magnification } .onEnded { value in staticScale = staticScale * value.magnification dynamicScale = 1.0 } }
Gesture Best Practices
-
Attach gestures to appropriately sized views (avoid tiny targets for multi-touch)
-
SwiftUI automatically handles gesture recognition conflicts
-
Use
.simultaneousGesture()for gestures that should work together -
Use
.highPriorityGesture()to prioritize one gesture over another
Module 3: Filesystem Persistence
iOS Sandbox
-
All apps run in a secure sandbox with restricted filesystem access
-
Standard directories:
-
Application directory: Read-only, contains app executable and bundled resources
-
Documents directory: Stores user-created documents
-
Application Support: Stores app data not visible to the user
-
Caches directory: Temporary files not backed up to iCloud
-
-
Access directories using
URLstatic properties:
swiftlet documentsDir = URL.documentsDirectory let appSupportDir = URL.applicationSupportDirectory
File Operations
-
Build file paths using
.appendingPathComponent()and.appendingPathExtension():
swiftlet fileURL = documentsDir .appendingPathComponent("game") .appendingPathExtension("json") -
Read/write raw data using
Data:
swift// Write try data.write(to: fileURL) // Read let data = try Data(contentsOf: fileURL) -
Use
FileManagerfor file management operations: copy, delete, check existence
Bundle Resources
-
Access files shipped with your app using
Bundle.main:
swiftif let url = Bundle.main.url(forResource: "sample", withExtension: "json") { // Load the file } -
Automatically handles localization of resources
Module 4: Codable and JSON
Codable Conformance
-
Codableis a combination ofEncodableandDecodableprotocols -
Automatic synthesis for structs and enums with all Codable properties:
swiftstruct Game: Codable { let name: String let score: Int let date: Date } -
For classes or complex types, implement manual conformance:
-
Define
CodingKeysenum mapping properties to JSON keys -
Implement
init(from decoder: Decoder)for decoding -
Implement
encode(to encoder: Encoder)for encoding
-
JSON Serialization
-
Encode to JSON:
swiftlet encoder = JSONEncoder() encoder.outputFormatting = .prettyPrinted let jsonData = try encoder.encode(game) -
Decode from JSON:
swiftlet decoder = JSONDecoder() let game = try decoder.decode(Game.self, from: jsonData) -
JSON is always UTF-8 encoded
Module 5: UserDefaults
-
Simple key-value store for small amounts of data
-
Access the standard instance:
swiftlet defaults = UserDefaults.standard -
Store values:
swiftdefaults.set(100, forKey: "highScore") defaults.set("Player1", forKey: "username") -
Retrieve values:
swiftlet highScore = defaults.integer(forKey: "highScore") let username = defaults.string(forKey: "username") ?? "Guest" -
Limitations:
-
Only supports property list types
-
Not suitable for large amounts of data
-
Not encrypted
-
Changes are written to disk periodically
-
Need me to prepare a complete reference sheet with all the code examples from this lecture formatted and ready to drop into your final project?


