Launch Lab Notes
Demo-first study notes. Each chapter builds one original bench in a real app, films it, and explains the idea from the footage.
28 chapters · about 637 min end to end
- Five Curves, One Gun: What SwiftUI Is Actually Doing When It AnimatesBuild a five-lane animation drag race in SwiftUI, film it, and read the footage: implicit vs explicit animation, the four timing curves, springs that overshoot, why your percentage label jumps to 100 instantly, and how to prove the curve on screen is the curve in your code.Beginner · 32 min
- Twelve Ways to Arrive: Transitions, and the One That Refuses to RunTwelve SwiftUI transitions insert and remove together on one board — opacity, scale, slide, move, push, blurReplace, combined, asymmetric, a custom Transition, and identity. Then the trap: two tiles asking for the same .scale, and only one of them gets it.Beginner · 28 min
- The Same View in Two Places: matchedGeometryEffect, and What It Doesn't DoBuild a hero handoff in SwiftUI — a release card expanding into a detail panel, with its icon, title and version chip flying between two entirely separate views. Then the three ways it goes wrong: matching text by frame, forgetting the shell, and expecting it to morph.Intermediate · 26 min
- Shapes Have to Be Redrawn: Animatable, animatableData, and AnimatablePairFour SwiftUI shapes animating side by side — a trim ring that works for free, a polygon that morphs through fractional side counts, the identical polygon that snaps because it has no animatableData, and a wave tank driven by AnimatablePair.Intermediate · 24 min
- Which View Is Which: Identity, and Why Your State Ends Up on the Wrong RowTwo SwiftUI lists over the same array — one keyed by a stable id, one by index. Insert a row at the top and watch three rows keep state that no longer belongs to them. Structural vs explicit identity, ForEach keying, .id() as a reset switch, and why every previous chapter depended on this.Intermediate · 26 min
- Who Had to Run Again: Observation, and the Two Things That Quietly Defeat ItSix SwiftUI cells with visible body-run counters, one changing property. @Observable leaves two cells at one run while ObservableObject re-runs all three — plus the two mistakes that erased the difference entirely on the first build: an @StateObject in the parent, and a closure stored in a view.Intermediate · 25 min
- The Parent Proposes, the Child Decides: SwiftUI Layout as a NegotiationFive SwiftUI views offered exactly the same width, each reporting what it actually took. Text wraps, Rectangle follows the offer, Image ignores it, and .fixedSize() grows straight through the box — plus why .frame() does not resize anything.Intermediate · 24 min
- The Interface You Can't See: Accessibility, Measured Rather Than AssumedThe same SwiftUI card built twice, then run through Dynamic Type and dumped from the real accessibility tree with XCUITest. One card ignores the text-size setting entirely, exposes an 18×18 button labelled 'Selected', and never mentions its status at all.Intermediate · 24 min
- Before the Data Arrives: Four States, and the Dash That Replaces ThemOne ManifestInbox fetch drives two panels. The left only draws .loaded and stays a dash through loading, empty, and failed. The right switches on all four. Measured: 1,093 ms mid-flight, empty at 790 ms, ASC 409 at 791 ms, then both panels finally agree on three rows.Intermediate · 24 min
- Who Keeps Working: .task Cancels, onAppear + Task Does NotTwo identical tick loops. While hosted both read 19. After leave: onAppear+Task is at 41 and still climbing; .task froze at 25. Revisit and the leaky lane spawns orphans — filmstrip ends at orphans 4, 268 vs 76.Intermediate · 22 min
- The Wrong Total: A Data Race You Can Read Off the ScreenTwenty tasks credit a ledger 10,000 times. A plain class finishes at 1,330 — lost 8,670. An actor finishes at 10,000 exact. The wrong number is the demo.Intermediate · 22 min
- Two Debounces, One Tape: Combine and async/await Wait for the Same QuietOne scripted search for Parcel Wire. RAW commits all 11 keystrokes. Combine debounce and an async sleep+cancel both emit twice — Parcel at ~804/837 ms, Parcel Wire at ~1925/1958 ms.Intermediate · 22 min
- What Survives a Relaunch: Memory, Defaults, JSON, and SwiftDataWrite Harbor build 418 four ways, kill the process, launch again. @State is gone (—). UserDefaults, a Documents JSON file, and SwiftData all still show the note.Intermediate · 20 min
- The Schema Moved: keyNotFound on Disk, Migration in the Other ColumnA schema-1 note file still holds Harbor build 418. Strict v2 decode fails with DecodingError.keyNotFound for build. Migrate upgrades first and prints schema 2 · build 418.Intermediate · 20 min
- Ten Thousand RowsSame 10,000 shipment rows in List, LazyVStack, and an eager VStack. List built 15 bodies in 746 ms. Lazy built 23 in 746 ms. Eager built all 10,000 in 3,202 ms.Intermediate · 22 min
- Three Levels, One PathOne append deep-links Harbor › v1.4.0 › build 418 — PATH depth 3, LINKS stays 0. Pop clears the array. After a real relaunch, PATH restores to 3; Links wakes at root.Intermediate · 22 min
- Who Won the DragLong-press arms PRIORITY, then a vertical drag moves it 72 pt. PLAIN uses the same DragGesture without highPriority — the ScrollView wins and the card stays at 0 pt.Intermediate · 20 min
- Down the Tree, Up the TreeParent pushes harborAccent down — WITH follows mint/rose/violet, WITHOUT stays gray. PreferenceKey sums App 72 + Build 96 = 168 pt; MUTE never reports.Intermediate · 20 min
- What the Subviews AnsweredSame five chips at 300, 210, and 150 pt. FlowLayout wraps to 2 then 3 rows; HStack stays one clipped row. sizeThatFits answers: A 64×36 … E 96×36.Intermediate · 20 min
- What the Tests CatchSame ship gate: HEALTHY both 2/2. VM BUG leaves UNIT at 0/2 while UI stays 2/2. UI BUG flips the label check to 1/2. XCUITest still finds ship-418 under the ViewModel bug.Intermediate · 22 min
- Swap the ServiceOne Release Pulse screen: HARDWIRED Live ignores -mode; INJECTED LIVE and STUB both READY Harbor 418; FAIL shows NETWORK DOWN while the control stays READY.Intermediate · 22 min
- Same Feature, Three BodiesShip build +1 as MV, MVVM, and a reducer. After 5 bumps: MV BODY 6; MVVM and REDUCER DIGIT 6 with CHROME still 1.Intermediate · 24 min
- Full Res vs ThumbnailsTwelve Harbor tiles. EMPTY live 18 MB. NAIVE Image(uiImage:) on 1536² hits live 236 MB / account 108.0. THUMB at 96² is live 21 MB / account 0.4.Intermediate · 22 min
- Four Stages, One StillCore Image strip: ORIGINAL control stays full color. NOIR → SEPIA → BLOOM each take the previous CIImage. Sepia pinned at 0.15, 0.55, and 1.00.Intermediate · 20 min
- One Uniform, Three EffectsSame Harbor card four ways. NONE is the control. colorEffect, distortionEffect, and layerEffect share one t — pinned at 0.00, 0.35, and 0.70.Intermediate · 22 min
- Same Numbers, Two StoriesHarbor week builds 400→415 (Δ 15 · 3.8%). HONEST bars from 0 are the control. Truncated Y 398–418 and a SectorMark pie tell louder false stories.Intermediate · 20 min
- The Entries AheadHarbor widget timeline as a filmstrip. AT END freezes STALE on build 415 after t+3h — the control. AFTER DATE shows RELOAD / getTimeline; wave 1 advances to 420–435.Intermediate · 22 min
- Three Sorts, One ArraySame seed 7 2 11 4 9 1 12 5 8 3 10 6. BUBBLE control ends at CMP 66. MERGE 32. QUICK 28. Mid-race at step 22: 22 / 15 / 18.Intermediate · 20 min