Project 3 — Skycast: the spec
Unit 23 · Project 3. A Weather-app clone — saved locations, city search, current
conditions, an hourly strip, a 10-day forecast, a temperature chart, and detail modules.
Backend Tier 0 (API-only — you call a public weather API, you persist nothing). AI stage still
Tutor — hand-typed, /tutor hints only. This spec sets the target; three milestones build
it, and the rubric closes it.
Project 3 is where your app first talks to the world. Until now data came from memory (P1) or the disk (P2); now it arrives over the network, asynchronously, and can fail. That single change — data that takes time and might not come — is what makes this project about states as much as networking. A weather app that shows a spinner that never ends, or a blank screen on airplane mode, is a weather app that fails the assignment.
What you build
Skycast — a Weather app with a locations list and a rich city detail:
- A locations screen: saved cities as cards (name, condition, temp, high/low) and a search that adds a city.
- A city detail: a big current header (temp, condition, H/L); an hourly strip with condition icons; a Swift Charts temperature graph; a 10-day forecast with condition icons and high/low range bars; and detail modules (feels-like, humidity, wind, UV, sunrise/sunset).
- Every screen renders loading, error (with retry), and permission states as deliberately as the success state.
The screens
| Screen | Content | Key interaction |
|---|---|---|
| Locations | saved city cards; a search field | tap a city; search to add one |
| City detail | header, hourly, temp chart, 10-day, detail modules | scroll |
| States | loading, error+retry, location-permission variants | retry, grant location |
What it looks like finished
LoadState switch driven off the view model.Concepts this locks in
URLSessionwithasync/awaitfor networking (no completion handlers).Codablemodels decoding a real API's JSON — current conditions, an hourly array, and a daily array — withCodingKeys.- A typed
APIClientwith anEndpointabstraction — notURL(string:)!scattered around. - An
@Observable @MainActorview model owning aLoadStateenum, rendered in every case. - Swift Charts for the temperature graph, and custom SwiftUI dataviz (the 10-day range bars).
- Mapping a weather-code to an SF Symbol + label, with day/night variants.
- List + search (
.searchable) and value-based navigation between cities. - CoreLocation for "here," with a permission flow and a graceful denied state.
Backend tier: 0 (API)
Tier 0 here means read-only from a public API, nothing stored. You don't run a server and you don't persist responses. The public Open-Meteo API needs no key and returns JSON forecasts — a clean target for practicing the networking stack without auth ceremony. (Persisting the saved-locations list is a one-model SwiftData addition you already know from Project 2 — a stretch, not part of the graded Tier 0 core.)
AI stage: Tutor
Last Tutor-stage project. You type all code; /tutor gives concept hints. Async networking has
specific traps (decoding mismatches, the view model actor, cancellation) — hit one, ask a
narrow question, fix it yourself. After this, the ladder moves to Pair.
Build in layers and test each before stacking the next: models decode (Milestone 1) → the view model drives states and the detail screen (Milestone 2) → the locations list, search, and location (Milestone 3). If you wire the UI to a live call on day one, every bug is ambiguous — is it the request, the decode, the state, or the view? Isolate.
In scope
- Fetch current + hourly + 10-day forecast for a location over async
URLSession. - Decode with
Codable; a typed client with anEndpointtype. - A view model exposing a
LoadState, and a detail screen that renders all cases. - The full detail: header, hourly strip with icons, a Swift Charts temperature graph, a 10-day forecast with range bars, and the detail modules.
- A saved-locations list and a city search that adds one.
- Current-location support with a permission flow and a graceful denied state.
- Light and dark correct, VoiceOver-navigable, no key or secret in the repo.
Out of scope
- Persisting the saved-locations list (a SwiftData stretch — Tier 0 core keeps it in memory).
- Weather maps / radar, severe-weather alerts, and minute-by-minute precipitation.
- Widgets, notifications, or a settings screen.
- Multiple providers or an API that needs an account/key.
Definition of done
Skycast lists saved cities, opens a city to a full detail (current conditions, hourly, a temperature chart, a 10-day forecast, and the modules), searches for and adds a city, spins a proper loading state while it waits, shows a retryable error on airplane mode, and asks for location permission the first time — degrading gracefully if denied. Then the rubric.
Knowledge check
Q: Why are loading and error states treated as first-class requirements in this project and not the earlier ones? Because network data takes time and can fail — states P1 (memory) and P2 (local disk) never really had. A weather app is defined as much by its spinner and its offline message as by its success case, so those states are graded, not optional.
Q: What does Tier 0 (API) mean for what you store? Nothing is persisted in the graded core. You read from a public API each session; there's no server you run and no local cache required. Persisting saved locations is a stretch built on Project 2's SwiftData, not part of the tier.