One. Course Details
This is the twelfth lecture of Stanford University's CS193p iOS Application Development course for Spring 2025, taught by Paul Hegarty. The entire session is dedicated to hands-on demos, completing the game editor functionality started in Lecture Eleven and adding advanced features to the CodeBreaker application.
The lecture covers building reusable UI components, presenting modal sheets for editing, implementing form validation with alerts, managing state with custom bindings, adding swipe actions for cross-platform interaction, and implementing a robust timer system with proper lifecycle management. It concludes with a demonstration of creating custom ViewModifiers to encapsulate reusable logic. The next two lectures will focus entirely on SwiftData for persistent data storage.
Two. Key Learning Takeaways
Sheets are used for modal presentations that require immediate user attention, automatically animating on and off screen.
@Environment(.dismiss) provides a clean way to dismiss modal views from within the view itself, eliminating the need to pass bindings down the view hierarchy.
TextField supports numerous modifiers to control input behavior, including autocapitalization, autocorrection, and submit actions.
Always validate user input before accepting changes, either by disabling invalid actions or presenting informative alerts.
Use the copy pattern when editing existing objects to allow cancellation without modifying the original data until the user confirms changes.
Custom bindings created with Binding(get:set:) eliminate duplicate state variables and keep your code clean and maintainable.
Swipe actions provide quick access to common operations on iOS, complementing context menus which work across all platforms.
Manage timer lifecycle using onAppear/onDisappear, onChange, and scenePhase to ensure timers only run when the relevant view is active.
ViewModifiers are the most powerful mechanism for reusing UI logic in SwiftUI, encapsulating both view modifications and state management.
scenePhase tracks your application's execution state (active, inactive, background) allowing you to pause and resume operations appropriately.
Three. Course Gold Quotes
"Modal UI should always be cancellable. Never trap your users in a mode they can't escape." "Clean code is concise code. Every line should only do things that matter." "@Bindable is just permission to use the $ sign on @Observable objects. That's all it is." "Alerts are specialized sheets that appear in the center of the screen." "ViewModifiers are the most powerful way to reuse UI logic in SwiftUI." "Always validate user input. Never let users create invalid objects." "Structs copy automatically, classes require manual copying." "scenePhase tells you when your app is active, inactive, or in the background." "Destructive actions should look destructive. Use role: .destructive to make them stand out." "Never have two pieces of state that represent the same thing. It always leads to bugs."Four. Layered Learning Notes
Module 1: Reusable UI Components
Start by extracting the peg selection logic into a dedicated PegChoicesChooser view to keep your code organized and reusable. This view only needs a binding to an array of Pegs, not the entire game object, following the principle of minimal data passing.
-
Accept title, system image, color, and action as parameters
-
Use
.tint()to apply the specified color to the button -
Wrap all actions in
withAnimationto ensure consistent transitions -
Use HStack to combine the image and text for a polished look
Module 2: Modal Presentations with Sheets
Sheets are the standard way to present modal interfaces in SwiftUI. They slide up from the bottom on iPhone and appear as centered cards on iPad:-
Use the
.sheet(isPresented:)modifier to control when the sheet appears -
The
isPresentedparameter takes a binding to a boolean value -
Sheets automatically include swipe-to-dismiss functionality
-
Use the
onDismissclosure to perform cleanup or save changes when the sheet is dismissed
For better encapsulation, move all toolbar and navigation logic inside the modal view itself. Use @Environment(\.dismiss) to dismiss the sheet from within the modal view, rather than passing a binding down from the parent. This makes the modal view self-contained and reusable.
-
.cancellationActionfor cancel buttons (placed on the leading edge) -
.confirmationActionfor done/save buttons (placed on the trailing edge and automatically bolded)
Module 3: TextField and Form Input
TextField provides numerous modifiers to customize input behavior:-
.onSubmit { }: Executes when the user presses the return key on the keyboard -
.autocapitalization(.words): Automatically capitalizes the first letter of each word -
.autocorrectionDisabled(true): Disables autocorrect suggestions -
.textContentType(.name): Provides hints to the system about the type of input expected
Module 4: Form Validation and Alerts
Validate user input to ensure only valid objects are created. Add anisValid computed property to your model (or in an extension if you don't control the model source) that checks:
-
The name is not empty
-
There are at least two unique peg choices (using a Set to eliminate duplicates)
-
Disable the done button when the input is invalid using the
.disabled(!game.isValid)modifier -
Allow the button to be pressed but present an alert explaining why the input is invalid
-
Use the
.alert(title:isPresented:)modifier -
Include buttons for user actions (typically an OK button)
-
Provide a descriptive message explaining the error
-
Set the
isPresentedbinding to true when you want to show the alert
Module 5: Editing Existing Objects
When editing existing objects, use the copy pattern to allow cancellation without modifying the original data:-
Create a copy of the object to be edited
-
Pass the copy to the editor view
-
If the user confirms changes, replace the original object with the edited copy
-
If the user cancels, discard the copy and leave the original unchanged
-
Changes are only applied when the user explicitly confirms them
-
Canceling automatically reverts all unsaved changes
-
Changing the game's pegs automatically restarts the game as a side effect
Module 6: Advanced State Management with Custom Bindings
Eliminate duplicate state variables by creating custom bindings that derive their value from existing state. For example, instead of having separateshowGameEditor and gameToEdit variables, create a custom binding for showGameEditor that is derived from whether gameToEdit is nil:swift
var showGameEditor: Binding<Bool> {
Binding(
get: { gameToEdit != nil },
set: { if !$0 { gameToEdit = nil } }
)
}This approach:
-
Eliminates the need for
onChangeto sync the two variables -
Automatically handles dismissal of the sheet
-
Keeps your code clean and reduces the chance of bugs from out-of-sync state
Module 7: Cross-Platform Interactions
Add swipe actions to provide quick access to common operations on iOS:-
Use the
.swipeActions(edge:)modifier to add buttons that appear when the user swipes a row -
Specify the edge (
.leadingor.trailing) where the actions should appear -
Use
.tint()to set the background color of the swipe buttons
-
Swipe actions work on iOS but not on Mac
-
Context menus work on both platforms (via right-click on Mac and long-press on iOS)
-
Include important actions in both places for maximum accessibility
Module 8: Timer Lifecycle Management
Implement a robust timer system that only runs when the game is active:-
Add
startTimer()andpauseTimer()methods to your model -
Track elapsed time using a combination of a start date and an accumulated time interval
-
Start the timer when the view appears using
onAppear -
Pause the timer when the view disappears using
onDisappear
-
On iPad, views are reused rather than recreated, so use
onChange(of: game)to stop the old game's timer and start the new one -
Use
scenePhasefrom the environment to pause the timer when the app enters the background and resume it when it becomes active again
Module 9: Reusable Logic with ViewModifiers
Encapsulate repeated logic into custom ViewModifiers to keep your view code clean and maintainable. For example, create anElapsedTimeTracker ViewModifier that handles all the timer lifecycle logic:
-
Conform to the
ViewModifierprotocol -
Accept the game as a parameter
-
Include the
@Environment(\.scenePhase)property -
Implement the
body(content:)method to apply the necessary modifiers to the content view
-
Eliminates duplicate code across multiple views
-
Encapsulates all related logic in a single place
-
Makes your view code more declarative and easier to read
-
May you master SwiftUI's advanced patterns and build beautiful, maintainable applications with clean state management and smooth user interactions. May your forms be intuitive, your timers be accurate, and your Assignment 4 word game shine with professional polish. As you continue your journey, may you embrace code reuse and create applications that scale effortlessly with your vision. Happy coding!


