iOS implementation of the air-quality booking assignment.
make setup # one-time: installs XcodeGen, trusts SPM macros, generates the project
# edit TADAAssign/Configuration/Secrets.xcconfig and paste your AQICN_API_KEY
make open # opens the project in XcodeThat's it. Cmd+R to run, Cmd+U to run tests.
If Xcode was already open before make setup ran, quit it (Cmd+Q) and run make open so the macro-trust defaults take effect.
make build # build for iPhone 17 Pro Simulator
make test # run the unit test suite (47 tests)
make clean # remove generated artifacts- Xcode 16+
- iOS 17+ deployment target
- Homebrew (for the
make install-toolsstep)
- SwiftUI + MapKit — code-based layout, no storyboards
- The Composable Architecture (TCA) — unidirectional data flow, state management, DI
- Alamofire — HTTP layer in
liveValueclients - XcodeGen — project generation from
project.yml(no.xcodeprojchurn in git)
TADAAssign/
├── App/ TCA root feature + app entry + navigation stack
├── Features/ One folder per screen (Map, LocationDetail, BookingConfirmation, History, CachedLocations)
│ Each has a Reducer (.swift) + View (.swift)
├── Clients/ TCA @DependencyClient interfaces with live + mock implementations
├── Models/ Domain models (Coordinate, LocationData, Book, SetLocation, LocationSlot)
├── Configuration/ AppConfig, APIConfig, MapConfig — no scattered constants
├── DesignSystem/ AppFont, Spacing, CornerRadius, LayoutMetrics, AppColor
├── Strings/ L10n typed accessor; strings in Resources/en.lproj/Localizable.strings
├── MockData/ Sample data used by mock clients (kept out of business logic)
└── Resources/ Info.plist, Assets, entitlements, .strings
Every dependency client (AirQualityClient, ReverseGeocodingClient, BookingClient, LocationManagerClient, LocationCacheClient) defines:
liveValue— real Alamofire / CoreLocation implementationmockValue/previewValue— sample-data implementation, with data sourced fromMockData/testValue— unimplemented stubs (generated by@DependencyClient)
The reducers depend only on the interface. liveValue contains zero hardcoded sample data.
Because the assignment provides no /books backend, the app opts into BookingClient.mockValue at startup via a single line in TADAAssignApp.swift. Swapping to real is a one-line change.
| Lives in | What |
|---|---|
Configuration/AppConfig.swift |
nickname max length, coordinate cache precision |
Configuration/APIConfig.swift |
base URLs, mocked network delay |
Configuration/MapConfig.swift |
fallback coordinate, default zoom span |
Keys live in TADAAssign/Configuration/Secrets.xcconfig. The file is committed with empty values; paste your own locally and strip them again before pushing.
| Key | Required? | Notes |
|---|---|---|
AQICN_API_KEY |
Required for real AQI data. Falls back to AQICN's "demo" token (sample stations) if empty. |
Free at https://aqicn.org/data-platform/token/ |
ADDRESS_API_KEY |
Optional BigDataCloud key (bdc_…). Falls back to the keyless free tier if empty. |
https://www.bigdatacloud.com/ |
At runtime, APIKeyProvider reads both from Info.plist, which is preprocessed at build time with the xcconfig values.
- Screen 1 (Map): full-width map with a black center pin (per Figma), top-right AQI badge updating on drag, A/B labels, V button state machine (Set A → Set B → Book)
- Screen 2 (Detail): location info + nickname input (20 char max); back-with-V-button saves nickname
- Screen 3 (Confirmation): POST /books mock, shows A/B/price; back button resets state
- Screen 4 (History): GET /books?year=&month= mock, total count + total price + list
- Unidirectional data flow (TCA)
- Clean architecture (Models / Clients / Features / DesignSystem / Configuration)
- Full DI (TCA
@Dependency, switchable mock ↔ live) - Coordinate caching (3rd-decimal truncation; same-location matching from spec)
- Screen 5 (cached locations) reached by tapping an unset A/B label
- State reset when returning from booking confirmation
- History tap → Screen 1 pre-filled with selected booking, AQI re-fetched in parallel
make test47 tests across 7 suites:
CoordinateTests— cache key truncation, same/different location matching, negative coordinatesMapFeatureTests— initial state, button progression, onAppear, map drag, set A/B, cache reuse, set-failure alert, booking, label taps, prefill from history, set from cacheLocationDetailFeatureTests— nickname init, 20-char truncation, save with/without nickname, slot BBookingConfirmationFeatureTests— view-history + back-to-map delegatesHistoryFeatureTests— load books, totals, selection, error handling, idempotent onAppearCachedLocationsFeatureTests— load cache, select for slot A/BAppFeatureTests— navigation pushes, nickname propagation, reset on back, history selection, cached → map
Xcode 16+ verifies SPM macro plugin binaries by fingerprint (security feature). Each time a macro plugin is rebuilt, Xcode wants explicit re-approval. The defaults write commands in make trust-macros pre-approve them at the user level. There is no project-level way to disable this validation — that's intentional, so a project can't silently auto-trust its own macro code.