Note Wisdom
These annotated notes break down Stanford CS193p lecture 11, covering cross‑platform SwiftUI for iPad and Mac, NavigationSplitView, selection state, context menus, and building a form‑based game editor.
Institution: Stanford
Original Course: Stanford CS193p: iOS Development with SwiftUI | 2025 | L11: iPad and Mac
Instructor Bio: This lecture is delivered by Paul Hegarty, Lecturer in Computer Science at Stanford University and the principal instructor of CS 193P since 2010. Paul Hegarty is a veteran software engineer and educator with deep roots in the Apple developer ecosystem. Earlier in his career he worked at NeXT Computer, where he contributed to the Objective-C language and the foundational tools that would eventually become Apple's modern development platform. He has taught iOS application development at Stanford for over fifteen years, guiding thousands of students through the transition from earlier UIKit frameworks to the modern SwiftUI declarative paradigm. He is widely recognized for his methodical teaching style, his emphasis on clean architectural patterns such as MVVM, and his ability to explain complex systems concepts through hands-on, live-coding demonstrations.
Course Description: This lecture addresses adapting iOS applications for larger screen sizes and multiple Apple platforms. It covers adaptive layout techniques using GeometryReader and size classes, split view and sidebar navigation for iPad, and how SwiftUI's declarative model enables the same codebase to run on iOS, iPadOS, and macOS with minimal modification. The lecture also discusses platform-specific considerations including pointer support, keyboard shortcuts, window management, and the design patterns that make apps feel native on each device.
NavigationSplitView, along with view refactoring, selection state management, context menus, and building a form‑based game editor. The lecturer warned upfront that the lesson would end mid‑task, and we would pick up unfinished work in the next class. The keyword here is cross‑platform SwiftUI, which threads through nearly every demo and code change shown in this session.NavigationStack for NavigationSplitView.NavigationStack works like a deck of cards. Every NavigationLink pushes a new view card on top, and the back button pops it back off. This is the standard iPhone navigation pattern. NavigationSplitView divides the screen into up to three panes: a left sidebar, optional middle content pane, and a right detail pane. In their Codebreaker demo they only used two panes, sidebar for the game list and detail for the active game.NavigationSplitView you will hit a compile error. Unlike NavigationStack, it expects content for your detail pane. For the initial empty state, they put simple placeholder text prompting users to pick a game..navigationSplitViewStyle(.balanced) modifier to divide screen space between both panes. This keeps both views visible simultaneously on larger displays.columnVisibility to control which panes show on launch. One interesting trick they walked through was using a constant binding set to .all. This tells SwiftUI you want all panes shown on startup, while still letting users manually collapse or expand panes via the UI. You do not need a dedicated @State variable if you don’t intend to programmatically override user‑triggered visibility changes.NavigationSplitView behaves exactly like a regular NavigationStack, pushing views full‑screen. On regular horizontal size classes, like iPad or large iPhones in landscape, it renders as multi‑column split view. You do not need to write conditional code checking for iPad versus iPhone. The lecturer actively discouraged writing device‑specific if checks unless you have very particular edge‑case needs..frame(maxHeight:) or .frame(maxWidth:) to set upper bounds while still letting views expand when space permits. Magic number constants get pulled out into struct‑stored constants for easier future tweaks..navigationTitle() to your sub‑views. The navigation system reads this value and renders it in the navigation bar. There is also .navigationTitleDisplayMode(.inline) to shrink large titles and keep them inside the navigation bar rather than taking up extra vertical screen space.NavigationSplitView, they hit a new problem. The built‑in implicit list selection stops working once you want to react programmatically to what item the user tapped. You have to create your own optional @State variable to hold the selected model object.List, each NavigationLink with a value parameter will automatically update this binding when tapped. Conversely, if you manually set your selection state in code, the list will treat that as if the user tapped that entry.navigationDestination. Inside the detail builder closure you use optional binding on your selection state. If something is selected, render the corresponding game view. If nothing is selected, show the placeholder “choose a game” text..onChange() modifier watching the games array. Whenever the list of games changed, check whether the currently selected item still exists inside the array. If it no longer exists, reset selection back to nil. I thought this was a smart approach because it works for every deletion pathway, including swipe‑to‑delete and context‑menu delete, not just one specific button action.onChange closure, they accidentally unwrapped the optional selection into a local let constant and then tried mutating that local copy, instead of assigning back to the original @State property. The lecturer pointed this out as a common mistake when working with optional unwrapping inside SwiftUI closures.
All contents below are exclusive to the paid Word file, NOT available on this web page
.contextMenu() view modifier. Context menus trigger via right‑click on Mac, or long‑press on iOS. Buttons inside context menus should supply both text labels and system image icons. On iOS, context menus visually expect icons next to every menu entry.ButtonRole.destructive. On iOS this colours the button text red to signal destructive actions. The lecturer noted this visual cue does not appear on Mac, even when you set the role. Any destructive action should be wrapped inside withAnimation so list entries animate out when deleted.GameList view. When you split views like this, any state you need to modify across parent and child has to be passed as a @Binding. The games collection itself was moved into GameList, and they added sample games inside .onAppear..onAppear triggers every time the view comes back on‑screen. On iPhone when navigating back, or when collapsing and expanding the sidebar on iPad, onAppear would fire repeatedly and keep appending duplicate sample games. The simple fix: only populate sample games if the games array is completely empty.@State preview‑only variables, or use .constant(nil). Wrapping your preview inside a NavigationStack is helpful so you can see toolbars and navigation UI elements that your view depends on.GameEditor view. This is meant for creating brand‑new games or editing existing ones. The core SwiftUI component here is Form. A Form is a pre‑built combination of scroll view, vertical stack, dividers, and styling. It is what iOS Settings app uses for all its screens. Forms automatically handle keyboard offset adjustments. When a text‑field becomes active and the on‑screen keyboard pops up, the form scrolls content so the text field stays visible above the keyboard.TextField for editing the game name. The first argument is the placeholder ghost text you see when the field is empty. The second parameter takes a binding to your string value. They hit a compiler quirk: when you want to create bindings to properties of an @Observable model object inside a child view, you need to mark those properties with @Bindable. Without this attribute you cannot use the dollar‑sign binding syntax on the observable’s stored properties.ColorPicker views inside a ForEach loop. They iterated over indices of the peg‑choices array, binding each colour picker directly to an element inside the array. This requires the array property to be declared as var, not let. If it is a constant you cannot bind into individual elements for editing. You can also add sections inside Form to group related inputs.GameEditor on screen from the main app. That material was deferred to the next class session. The lecturer noted this work would be relevant for the upcoming course assignment 4, which requires building a settings‑style editing interface. They also teased that future lectures will cover SwiftData for local persistent storage.@Environment to read horizontal size classes and branch your logic, but skipped writing that code for this lecture. I was left wondering what practical pattern they would recommend for handling that trade‑off in real‑world projects.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

