07‐Jackie - molab-itp/content-2026-itp GitHub Wiki
what programming languages have you used? how much?
- html/css/js (still beginner)
what is your favorite mobile app and why?
- dooo0t ! such a cute way to edit your pics~
if you have a concept for a final project write a short description
- not sure yet!
Week 03 — SwiftUI 1, Images
Homework: Week03/TenPrintTiles
How I approached learning Swift fundamentals
Coming from html/css/js, the thing I keep bumping into is that Swift wants to know the type of everything up front. In JS I can put anything in an array and move on. In Swift the array has one type and the compiler tells me about it immediately.
What has actually worked for me so far:
- Reading the teacher's sample code before writing my own. Weeks 01 and 02 I started by opening the sample playground pages, running them, and changing one value at a time to see what moved. The playground showing results in the right-hand column made this fast — I could see what a line did without a print statement.
- Building up from a sample rather than a blank file. My Week02 ascii image page
started as the teacher's
inter weavepage and turned into my own thing a few edits at a time. - Getting the loop and the array right on paper first. Especially for the grid stuff
— working out
rowandcolfrom a single index is easier drawn than typed.
Still shaky on: optionals and when to use ! vs ?, closures ({ $0.count } syntax
still reads as magic to me), and knowing which type a SwiftUI modifier expects.
Week 03 progress
This is the first week the work is a real Xcode app project instead of a playground, which was its own adjustment.
What I built: a My Emojis screen — a grid of my most used emojis (🥹😂🙂↔️🫶😊🥰🥲✋⚽️👵🏻🙃⚾️) that reshuffles into a new random arrangement every time you tap the Shuffle button.
It works the way the teacher's 10Print example does: build an array of random elements
first, then draw the array. Every cell calls randomElement() on my emoji array, so
40 cells get filled from a list of 12 and the repeats fall wherever they fall.
let myEmojis = ["🥹", "😂", "🙂↔️", "🫶", "😊", "🥰",
"🥲", "✋", "⚽️", "👵🏻", "🙃", "⚾️"]
func makeEmojis() -> [String] {
var result: [String] = []
for _ in 0..<(emojiCols * emojiRows) {
result.append(myEmojis.randomElement()!)
}
return result
}
The array lives in @State, which is what makes Shuffle actually work — I learned that
a plain var changes the value but nothing on screen moves.
The grid itself is a nested ForEach — rows in a VStack, cells in an HStack — and
the screen sits in a TabView with .tabItem { Label(...) }, the multi view pattern
from 03-About-Me.
What I learned the most from: I started this screen by copying a version that drew
SF Symbols, expecting to just swap the names out for emoji. That did not work.
Image(systemName:) only takes SF Symbol names — emoji are text, so the screen
needs Text(emoji) with a big font instead.
Two follow-on differences I did not expect. Emoji carry their own color, so
.foregroundStyle() does nothing to them, which means there is no color to randomize —
the only random choice left is which emoji goes in each cell. That actually made my code
shorter than the symbol version, because I did not need a struct to pair an emoji with
a color. Just an array of strings.
Issues and errors
- Spent a while stuck because I opened the whole
00-molab-2026-jhtfolder in Xcode instead of opening a.playgroundfile directly. In folder mode the playgrounds show up in the sidebar but clicking them does nothing — no editor, no run button. A.playgroundis a bundle, not a file, so the folder browser can't open it. Plain.swiftfiles in the same folder opened fine, which is what made it confusing. - The
Image(systemName:)vsText()mixup above — the copied screen compiled fine but showed nothing useful until I understood emoji are text, not symbols. - The simulator remembers which tab was selected between launches, so my screenshots kept coming out on the wrong tab. Deleting the app from the simulator resets it.
AI use
Per the course AI policy: I used Claude Code to scaffold the Xcode project and the view
files, working from the teacher's samples. It also diagnosed the Xcode
folder-vs-playground problem above, and explained why emoji need Text() rather than
Image(systemName:). I did not use AI on the Part 0 TRY: exercises. Each source file
names the sample its technique came from so I can walk through the code in class.
Week 04 — SwiftUI 2, Audio
Homework: Week04/MinuteGarden
Writing this up late, catching my wiki back up.
Week 04 progress
What I built: MinuteGarden, a one minute timer. Press Start and a plant gets added to the garden every 10 seconds while birds play underneath. At zero the birds stop and a chime rings. Three tabs: Timer, Sounds, About.
It does both halves of the assignment — time (Timer.publish counting down, from
CountDownTimerView) and audio (AVAudioPlayer, from PlayAudioView). The Sounds tab
is honestly PlayAudioView with the names changed, which I would rather say out loud
than hide.
The garden is just one String. Adding a plant is:
garden = garden + plants.randomElement()!
and showing it is Text(garden). No grid, no row and column arithmetic.
What I learned the most from: I built a bigger version first — MinuteGardenPlus —
with an @Observable class shared across pages with .environment(), a Track struct,
selectable ambience, and a ProgressView driven by TimelineView(.animation). All
Week04 material, and it worked. I dropped it anyway, because it was much more machinery
than the assignment needed and I could not confidently explain every line of it. It is
still in git history at commit 5eed76a.
The thing that decision taught me: @State only means each page owns its own stuff and
nothing is shared. So TimerView and SoundsView each have their own player, and if
you start the timer and then hit Play on the Sounds tab you get two sounds at once.
That bug is the explanation of what @Observable + .environment is for.
Issues and errors
Bundle.main.path(...)!force unwraps, so a misspelled file name crashes the app instead of printing an error. The sample does this too, but it was the first place I had to look when it died on Start.- The timer ticks whether or not the countdown is running —
.onReceivefires every second no matter what. Withoutif isRunning && timeRemaining > 0at the top of the block, the number goes negative. - One
AVAudioPlayeronly holds one sound. Reassigning it for the chime drops the looping birds, so the birds have to be stopped first or it gets weird. timeRemaining % 10 == 0grows a plant at 50, 40, 30, 20, 10 and 0 — that is 6 plants in a 60 second session, not 5. Zero counts.#Previewcrashes with no explanation if a view reads something out of@Environmentand the preview does not supply it. Not an issue in the version I kept, but it bit me repeatedly inMinuteGardenPlus.
Hours: [fill in]
AI use
Used Claude Code to scaffold the project from the teacher's samples and to talk through
whether the @Observable version was worth keeping. Each file names the sample its
technique came from.
Week 05 — Local Data 1, Sensors
Homework: Week05/TurnBack
[screenshot - need to run it on my phone first]
Week 05 progress
What I built: TurnBack, a navigation app that runs on the gyroscope. Press Mark while facing the way you came in, and the arrow keeps pointing back that way however you turn. Three pages browsed from a list: Heading, Level, About.
The demo I incorporated is @AppStorage, from
05-AppStorageDemo. Four emoji buttons
📍 🏠 🚗 🥾 pick what the marked spot is called, and the pick survives relaunch.
The thing I got wrong at the start: I thought "gyroscope" meant I could build a compass. It does not. The gyroscope only knows how far the phone has spun since the sensor started — it has no idea where north is. North needs the magnetometer, which we have not covered. So the arrow is relative to wherever I was standing when I pressed Mark, and it has to be marked again every launch.
That turned into the most interesting decision in the app. I am saving the label with
@AppStorage but deliberately not saving the marked direction, even though saving a
Double would be one line. A bearing restored from yesterday would be meaningless,
because the sensor picks a brand new zero every time it starts. So: what can be saved
and what is worth saving are two different questions.
The maths is just subtraction. detector.yaw is how far the phone has spun, in radians;
subtract the yaw I saved when I pressed Mark, and turn the arrow by the difference with
.rotationEffect — the same trick the needle uses in NeedleSeismometer.
var turnAngle: Double {
var diff = detector.yaw - markedYaw
if diff > Double.pi { diff = diff - 2 * Double.pi }
if diff < -Double.pi { diff = diff + 2 * Double.pi }
return diff
}
Those two ifs matter: yaw wraps around at -pi and pi, so without them the
difference can come out as more than half a turn and the arrow swings the wrong way
round.
What I changed in the sample code. MotionDetector.swift, DoubleExtension.swift,
BubbleLevel.swift and OrientationDataView.swift are copied from
05-BubbleLevel. Two changes, both
commented in the files:
- Added
yawtoMotionDetector— the sample tracks roll, pitch and z acceleration, but not yaw, which is the one thing my app actually needs. - Took
start()/stop()out ofLevelView, because my page list starts and stops the detector for the whole app the waySeismometerBrowserdoes.
The bubble level earns its place rather than being decoration: yaw only reads true with the phone held flat, face up. Tilt it upright and roll, pitch and yaw start mixing into each other.
Issues and errors
- Nothing moves in the simulator. There is no gyroscope in it, so
yawsits at 0 and the arrow never turns. Both class samples are listed under "Device Required" and now I know why. Layout can be checked in the simulator; behavior cannot. - Leaving
start()/stop()inLevelViewas well as the page list killed the sensor every time I navigated back to the list. Start and stop belong in exactly one place. - Tried to build straight to my phone and got "The developer disk image could not be
mounted on this device" twice — I think because the phone was locked. Still to sort
out:
[ ]run it on the phone from Xcode. - Still to verify on the phone: which way the arrow turns. I worked out the sign by
reasoning about the phone lying face up, but that is the kind of thing you confirm by
standing up and turning around, not on paper. If it tracks my turn instead of pointing
back, it is a one character fix (flip the minus in
turnAngle).
Hours: [fill in]
Plans and questions
- Measure the drift. A gyroscope adds up rotation over time, so small errors accumulate. Mark a direction, walk a loop around the floor, come back to the same spot and see how far off the arrow is. That is a measurement, not a guess.
- Why does this app share one
MotionDetectoracross pages when MinuteGarden deliberately gave each page its own player? I think two pages reading the same live sensor is exactly the case@Observable+.environmentexists for — would like to check that reasoning in class. MotionDetectorusesTimer.scheduledTimerand asks the sensor for its current value. Week04 usedTimer.publish(...).autoconnect()with.onReceive. Both make something happen every so often — when is each the right one?
AI use
Per the course AI policy: I used Claude Code to scaffold the Xcode project, copy in the sample files, and write the Heading page. It is also what pushed back on my original "compass" idea and explained that the gyroscope cannot find north, which changed the whole concept. Every file names the sample its technique came from, and the sensor reasoning is something I still need to confirm by walking around with the phone rather than taking on faith.
Week 06 — Local Data 2, Files
Homework: Week06/TurnBack
Week 06 progress
What I built: TurnBack again, plus My Walks, a drift log saved as a JSON file in the app's Documents folder. The saving option I picked is Codable + JSON, from 06-ImageEditDemoJSON.
Last week I worked out that the marked direction is not worth saving, because the sensor picks a new zero every launch. This week's answer to "so what is worth saving?" is how wrong the arrow gets. Mark a direction, walk a loop, come back to the same spot, face the way I marked, and tap I'm back. Whatever the arrow reads at that moment is the gyroscope's drift. Each walk is saved with the place, the date, the minutes I was gone and the degrees off.
The saving follows the sample's three pieces:
DriftEntry— one walk, anIdentifiable, Codablestruct (the sample'sItemModel)Model— the list of entries; loads itself from the file and saves itself back (the sample'sModel+ModelJSON, merged into one file)Document— an@Observableclass shared with.environment, which saves after every change, so there is no Save button to forget
SaveLoadJSON.swift is copied from the sample unchanged. I use its generic
saveJSON / loadJSON without having written the <T: Encodable> part myself.
struct DriftEntry: Identifiable, Codable {
var id = UUID()
var emoji = ""
var name = ""
var date = Date()
var minutes = 0.0
var driftDegrees = 0.0
}
UX pass. I also redesigned both screens with a designer's eye:
- Renamed Heading → Way Back and Drift Log → My Walks: say what it's for, not which sensor it uses
- One main action per step. Before marking: pick the place, then one big Mark this direction button. While walking: the arrow and one big I'm back - save walk button. The place picker hides mid-walk
- Touch targets: every button is at least 44 pt tall (Apple's minimum), and the main buttons are 56 pt and full width in the thumb zone
- Words, not raw numbers: "12° to your right" instead of "+012°"
- The answer first: My Walks opens with the average drift in big type
- Destructive actions are small and far away. Start over sits well below
I'm back, and Clear all walks needs two taps (done with just
@State)
Issues and errors
- Every
#Previewnow needs.environment(Document())as well as the detector — same crash-with-no-explanation as Weeks 04 and 05. - The first launch prints
loadJSON NO file at filePath. That's expected, not a bug: there is no file until the first save. - The simulator can test saving but not drift. Every walk reads 0° because there's
no gyroscope. The saving test is: save a walk, stop the app, run it again, and the walk
should still be in the list.
[confirm I did this] [fill in anything else I hit]
Hours: [fill in]
Plans and questions
- Run it on my phone and log real walks, a short one and a long one. Does drift grow
with time or with how much I turn?
- Walk 1 (Oct 14, on my iPhone): 3° off after a walk of under a minute. It works on the phone, saves, and shows up in My Walks.
- Next: a longer walk to compare.
[fill in]
- If I add a property to
DriftEntrylater, what happens to walks already saved in the old file? (I think they stop loading and the list comes up empty.) - Why save after every change instead of when the app closes?
- Final project direction: turn TurnBack into a "find my car" app for underground garages, where GPS drops out. Save a photo of the level sign (Week 07 Photo), the level and a note, and later share the spot with someone (Weeks 09–10). Real GPS and compass would need Core Location, which we haven't covered.
AI use
Per the course AI policy: I used Claude Code to brainstorm options for saving data, pick
the drift log idea (it came from my own Week 05 questions), scaffold the new files from
06-ImageEditDemoJSON, and do the UX review of both screens. Every file names the sample
its technique came from, and Week06/CLAUDE.md in my repo lists exactly what's new this
week so I can walk through it in class.