HeyWeather
A weather app built once and shipped four times: one Swift codebase spanning iPhone, Apple Watch, macOS, and visionOS, with widgets and Live Activities off its own data pipeline.
- Role
- Lead iOS Engineer (Contract) · GCO Apps
- Platform
- iPhone · Apple Watch · macOS · visionOS
- Stack
- Swift · SwiftUI · WidgetKit · ActivityKit
- Scale
- 4 platforms · 1 shared Swift codebase
Four platforms, one codebase.
HeyWeather ships across iPhone, Apple Watch, macOS, and visionOS from a single Swift codebase. Shared domain logic and a shared data pipeline sit underneath, with platform-native UI on top of each. I led the engagement for GCO Apps as a contract Lead iOS Engineer: the cross-platform architecture, and the WidgetKit widgets, Live Activities, and Dynamic Island integrations built on top of the app's own data pipeline.
The problem
A weather app is judged by four completely different surfaces at once: a full app on iPhone, a glanceable complication on the Watch, a menu-bar-adjacent presence on macOS, and a spatial one on visionOS. Building each natively from scratch would mean four codebases quietly drifting apart on the same underlying forecast. The brief was to reach all four platforms without paying for four separate implementations of the same product.
Constraints
- Four platforms, one small contract team — no capacity to hand-roll separate business logic per target.
- Genuinely different presentation idioms. A Watch complication, a macOS window, and a visionOS surface don't share a UI model. Sharing stopped at domain logic; the UI on each platform stayed native.
- WidgetKit and ActivityKit's own rules. Widgets, Live Activities, and Dynamic Island each impose refresh and lifecycle constraints the shared pipeline has to respect no matter which platform is asking.
- A contract engagement. The codebase had to be legible enough for GCO Apps' own team to take over after handoff. That mattered more than how quickly I alone could build it.
The pipeline behind every surface.
Domain logic and the data pipeline live in a shared Swift layer. Every platform target, and every system surface built on top of it, reads from the same place.
What stayed shared, what stayed native.
Shared core, native UI on each platform
Domain models and the data pipeline live in a shared Swift layer; every platform target builds its own UI on top in its own idiom. The boundary sits between domain logic and presentation. There's no single UI stretched across four radically different interaction models.
Widgets and Live Activities read the app's real pipeline
WidgetKit widgets and ActivityKit's Live Activities and Dynamic Island integrations consume the same data pipeline the app itself uses. There's no simplified, parallel source that could quietly drift from what the app actually shows.
Handoff shaped the architecture
As a contract engagement, the architecture was shaped so GCO Apps' own team could pick up ownership cleanly after delivery — a different bar than optimizing purely for one engineer's solo velocity.
What sharing a core actually costs.
A shared core over four native implementations
One place to fix a forecast bug, with consistent behavior guaranteed across every platform instead of merely hoped for.
The boundary between shared logic and platform UI needs constant discipline. It's easy for a platform-specific shortcut to start leaking into code every other target depends on.
Widgets sharing the app's real pipeline
What a widget or Live Activity shows always matches what the app itself would show, with no separate source of truth to fall out of sync.
The shared pipeline has to respect WidgetKit's and ActivityKit's stricter memory and refresh budgets even when it's running inside the main app rather than inside an extension.
Handoff-ready architecture over solo velocity
The client's own team could take over ownership cleanly once the contract ended.
Slower to build than fully undocumented, move-fast solo iteration would have been. Legibility for a future owner was a real, paid-for requirement, not a nice-to-have.
Four platforms don't share an interaction model.
Finding where sharing actually stops
The genuinely hard design question wasn't the data pipeline: it was deciding exactly where the shared layer ends and platform-specific presentation begins, without over-abstracting UI that legitimately differs between a Watch complication and a visionOS window.
Building against a moving platform surface
Live Activities and Dynamic Island were still relatively new system surfaces during this engagement, with real refresh and update constraints imposed by the OS rather than the app.
Designing for a handoff that hadn't happened yet
Structuring a contract build so a client team could take over ownership meant writing the shared core for a future reader. That's a different discipline than optimizing purely for one engineer's solo speed.
What one codebase covered.
A weather app still touches location.
Any weather app has to treat location as sensitive by default. On this engagement, that meant relying on platform-provided location APIs rather than a custom location pipeline, and keeping the widget and Live Activity surfaces reading from the same on-device data path as the app. No separate collection point was introduced purely for a system extension. Deeper backend-side privacy specifics belonged to GCO Apps and aren't mine to publish here.
A contract, phase by phase.
Engagement starts, core architecture first
Joined GCO Apps as contract Lead iOS Engineer, and began with the shared Swift core (domain models and the data pipeline) before touching any platform-specific UI.
iPhone and Apple Watch ship first
The primary app and Watch complications were the first two surfaces built against the shared core, establishing the domain/UI boundary every later platform would follow.
macOS and visionOS join
Extended the same shared core to macOS and visionOS: new presentation layers, no changes to the underlying domain logic.
WidgetKit, Live Activities, Dynamic Island
Delivered widgets and ActivityKit-based Live Activities and Dynamic Island integrations, wired to the app's own data pipeline rather than a simplified feed.
Engagement closes
Delivered the codebase structured for GCO Apps' own team to own going forward. That was the contract's explicit success condition.
What four platforms taught.
- Share logic, not interaction models. The temptation to abstract UI across platforms is real; the platforms that make it worth resisting are the ones with genuinely different idioms. A Watch complication was never going to be a small iPhone screen.
- System surfaces should never have their own data source. A widget that reads a "simplified" feed instead of the app's real pipeline is a drift bug waiting to happen.
- Handoff is a requirement, not a courtesy. Building for a future owner changes real decisions: naming, boundaries, documentation, made well before anything ships.
Four platforms, delivered and handed off.
HeyWeather shipped across iPhone, Apple Watch, macOS, and visionOS from one Swift codebase between 2023 and 2024, with WidgetKit widgets and ActivityKit Live Activities and Dynamic Island integrations built on the app's own data pipeline rather than a separate feed. The engagement closed with the architecture handed to GCO Apps' own team — the measure of success for a contract like this one.