Humanus — Technical Architecture

Fully Apple-native,
zero dependencies.

A production iOS app built entirely on Apple frameworks — from weather and location to payments, remote config, and CI/CD.

UI
SwiftUI
State
@Observable / ObservableObject + Combine
Concurrency
Swift actors + async/await
Weather
WeatherKit
Location
CoreLocation + CLGeocoder
City search
MapKit (MKLocalSearchCompleter)
Payments
StoreKit 2
Remote config
CloudKit (public database)
Widget
WidgetKit + AppIntents
CI/CD
Xcode Cloud

Repository Structure

The app is split into two repositories mirroring how Apple structures its own frameworks: a product repo (RoamerTemp) and a shared packages repo (RoamerFoundation). Foundation packages are intentionally app-agnostic and are already shared across multiple Roamer products.

RoamerTemp (app) ├── Humanus.app │ ├── SwiftUI views │ ├── WeatherService (WeatherKit actor) │ ├── PromoService (CloudKit + StoreKit, app-specific) │ └── Entitlements (StoreKit 2 wrapper, app-specific) │ ├── HumanusWidgetExtension (WidgetKit) │ └── AppIntents entity query (custom location picker) │ └── HumanusIntentsPackage (AppIntents, shared with widget) RoamerFoundation (local Swift packages) ├── RoamerLocation CoreLocation manager + CLGeocoder ├── RoamerStoreKit StoreKit 2 controller + EntitlementsManager<T> └── RoamerCloudKit CloudKit fetcher, cache, sync queue, auth

Foundation packages are referenced as local Swift packages resolving via relative path. On Xcode Cloud, a ci_post_clone.sh script clones the Foundation repo alongside the app repo so the path reference resolves identically in CI and locally — no package registry, no SPM mirroring, no build configuration divergence.

Apple Frameworks in Use

WeatherKit

Weather is fetched through WeatherKit.WeatherService, wrapped in a Swift actor to serialise concurrent requests and avoid redundant network calls. The raw Celsius value is converted via a custom Humanus algorithm — a piecewise linear interpolation over 122 empirically mapped temperature points — before display or storage.

CoreLocation + CLGeocoder

RoamerLocation.LocationManager handles authorisation state, one-shot location requests, and timeout recovery. Reverse geocoding (coordinate → placename) is a single async static method used by both the app and the widget extension. No third-party geocoding or maps SDKs.

MapKit

The location picker uses MKLocalSearchCompleter — MapKit's native type-ahead engine — to provide live city search results biased to the user's current region. Results are resolved to coordinates via MKLocalSearch without leaving Apple's infrastructure.

StoreKit 2

RoamerStoreKit.StoreKitController monitors Transaction.updates in a background task. EntitlementsManager<T> is generic over the app's own ProductTier enum. Purchased tier IDs are JSON-encoded into a shared App Group UserDefaults key so the widget reads entitlements without an IPC round-trip.

CloudKit

RoamerCloudKit.CloudKitFetcher<T> is a generic record fetcher backed by a CacheManager that serves stale data synchronously while refreshing asynchronously. It powers the in-app CMS: promotional cards and targeting rules are authored in CloudKit Dashboard and fetched at runtime — no app update required for content changes.

WidgetKit + AppIntents

The widget is an AppIntentTimelineProvider configured by a custom intent. Premium users choose a location via an EntityQuery backed by WidgetLocationStore — a shared App Group store with a 20-entry recents list and an append-only UUID registry for stable entity resolution. Reverse geocoding runs in the timeline provider and writes results back so subsequent renders never re-geocode.

App Groups + UserDefaults

All cross-process data — entitlements, device location cache, widget recents, location registry, feature flags — travels through a single shared UserDefaults App Group suite. JSON encoding is used consistently throughout.

Universal Links + Custom URL Scheme

Deep links (humanus://location-picker, humanus://home) and universal links are handled in HumanusApp and dispatched to AppState flags drained by ContentView once the scene is active. This drives App Store in-app events and widget onboarding flows.

Xcode Cloud

Build, test, and TestFlight distribution run entirely on Xcode Cloud. A single post-clone script clones RoamerFoundation so local package paths resolve in CI identically to local dev. No third-party CI service, no Fastlane, no GitHub Actions.

Design Principles

01

No third-party dependencies

Every capability — weather, location, maps, payments, remote config — is met by an Apple framework. This eliminates licence risk, supply-chain risk, and the ongoing maintenance cost of keeping external SDKs current.

02

Generic packages, app-specific logic

Foundation packages are intentionally ignorant of Humanus. EntitlementsManager<T> doesn't know what a ProductTier is. App-specific enums and business rules stay in the app repo; packages are shared across all Roamer products.

03

Concurrency by design

Services are Swift actor types. UI state mutations happen on @MainActor. There are no DispatchQueue.main.async call sites; background work uses structured concurrency throughout.

04

Widget as a first-class citizen

The widget has its own weather fetcher, its own geocoder path, and reads entitlements and location data from App Group UserDefaults independently of the host app. It degrades gracefully at every failure point — missing location, expired entitlement, network error — without crashing or showing stale data silently.