Note Wisdom
These annotated notes for L12 explain SwiftUI complex UI techniques: reusable views, sheets, validation alerts, custom bindings, timer tracking, and view modifiers, including tricky edge‑cases across iPhone and iPad.
PegChoicesChooser. This view manages the list of peg color options. The reasoning here is straightforward: the main game editor should not own every piece of nested UI. Isolating this sub‑view makes previewing easier and keeps responsibilities narrow (1:00‑3:00).@Binding and @Bindable. @Binding is used for struct value‑type data passed between views. @Bindable works with reference‑type ObservableObject classes, letting you generate a $ binding to mutate the observed model. The instructor emphasizes a good habit: avoid passing an entire model object into a sub‑view if only one property is needed. For PegChoicesChooser, only the array of pegs is passed, not the whole game object.@escaping because the closure will get stored and run later when the user taps, instead of executing immediately during view construction. They also use the tint() view modifier to apply custom coloring. Green tint goes to add, red tint to delete. All button actions get wrapped inside withAnimation so inserting and deleting list rows animates smoothly (6:00‑10:00).@State variable. Since the preview needs direct access to that state variable, the property uses @Previewable and cannot be marked private. They also add an onChange modifier inside the preview to print changes to peg choices, so they can verify edits are working in the preview canvas without running the full app.sheet modifier for modal presentation (11:00‑14:00).isPresented value. When true, sheet appears; when false, sheet disappears. Sheets animate automatically. You do not need to wrap state changes in withAnimation to trigger the sheet transition.@State variable showGameEditor. Then they realize they need to track which game object is being edited. They switch to an optional game object gameToEdit. If this optional is non‑nil, the sheet should show.onChange(of:) to sync gameToEdit to the boolean presentation state. When gameToEdit becomes non‑nil, set showGameEditor to true. When it becomes nil, set it false. They also use sheet’s onDismiss callback to clean up state when the sheet closes.gameToEdit is optional, but the GameEditor view expects a non‑optional game argument. Inside the sheet’s view‑builder they use if‑let conditional unwrapping. The instructor notes that force‑unwrapping (!) could work during development but should be avoided for shipping code. If the optional were nil, the sheet would render empty content, which is a bad user experience.NavigationStack, so they embed the GameEditor inside one even though they are not doing actual navigation. They use ToolbarItem with semantic placements: .cancellationAction for Cancel and .confirmationAction for Done. SwiftUI automatically positions Cancel to the left and bolds Done on the right side.GameEditor. They add a callback closure parameter onChoose that runs when the user taps Done. Inside the editor they use the environment value @Environment(\.dismiss). Calling this dismiss function closes the sheet from within the modal view itself. This removes some of the state‑sync burden from the parent view.
Article + Bundle Download
Complete Annotated Notes (L1-L16)
All contents below are exclusive to the paid Word file, NOT available on this web page
isValid property as an extension on the model, treated as UI‑layer code..disabled() modifier. The instructor thinks this is not great UX. Users get confused when a button is greyed out with zero feedback explaining why it is disabled.alert. Alerts work similarly to sheets: they are view modifiers bound to a boolean presentation state. The alert displays a title, descriptive message, and confirmation button. The alert explains exactly what requirements are not satisfied, so users know what needs fixing (34:00‑41:00)..swipeActions) for the leading edge of list rows. A caveat is mentioned: swipe actions do not work on macOS, so context menus remain necessary for cross‑platform compatibility.Codebreaker is a class (reference type), passing the real game object into the editor means all edits mutate the original immediately. If the user taps Cancel, changes are already applied, which is wrong.gameToEdit and boolean showGameEditor. These two variables represent the same logical state, which creates maintenance overhead. They show how to construct a custom Binding using the get and set closure initializer.get returns gameToEdit != nil. The set only responds to being set to false (dismiss case), setting gameToEdit = nil. Setting the binding to true is ignored because there is no meaningful value to assign. This eliminates the separate boolean state variable and removes the need for the onChange sync and some onDismiss logic (48:00‑54:00).onAppear / onDisappear, but on iPad split view, views stay in memory and do not disappear when selection changes. So onAppear / onDisappear alone cannot reliably start and pause timers.startTimer() and pauseTimer(). Inside the model they store:
startTime: Date?elapsedTime: TimeIntervalstartTime, add it to elapsedTime, then set startTime back to nil. When starting, assign current date to startTime. Restarting a game resets both values. When the game finishes, the timer pauses automatically.onChange(of: game) with both old and new value parameters. When the selected game changes, pause the old game’s timer and start the new game’s timer.@Environment(\.scenePhase) environment variable. scenePhase can be .active, .inactive, .background. Using onChange(of: scenePhase), pause timers when moving to background and resume when becoming active. .inactive transitional state gets ignored.ViewModifier. A ViewModifier struct has a body(content: Content) method. The content parameter represents whatever view the modifier attaches to. The modifier can access environment values like scenePhase, then apply any number of view modifiers to content. They then add a convenience method on View so you can write something like .trackElapsedTime(inGame: game) instead of instantiating the modifier struct directly.scenePhase transition to .inactive does not trigger timer changes, so timing will have small inaccuracies during app state transitions. They accept this as acceptable trade‑off for practical code.Skip hours of watching lectures. Get organized notes, exam prep materials and problem solutions all in one Word file.
Click to see everything included
All contents below are exclusive to the paid Word file, NOT available on this web page
Article + Bundle Download
Complete Annotated Notes (L1-L16)

