Project 3 — Skycast: the specswift-6.4/ios-26
Lesson 1 / 5
Unit 23 · Skycast

Project 3 — Skycast: the spec

Note

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

Skycast locations — saved city cards with condition, temperature, and high/low Skycast city detail — current header, hourly strip, and a Swift Charts temperature graph Skycast detail modules — feels-like, humidity, wind, UV, sunrise, sunset Skycast city search — results to add a location
The finished build in the simulator: the locations list, a city's detail (current header, hourly strip, Swift Charts temperature graph, 10-day forecast), the detail modules, and the city search. The detail screen is one LoadState switch driven off the view model.

Concepts this locks in

  • URLSession with async/await for networking (no completion handlers).
  • Codable models decoding a real API's JSON — current conditions, an hourly array, and a daily array — with CodingKeys.
  • A typed APIClient with an Endpoint abstraction — not URL(string:)! scattered around.
  • An @Observable @MainActor view model owning a LoadState enum, 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.

Tip

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 an Endpoint type.
  • 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.