← All work
3M+ iOS users
1M+ daily active
100% crash-free

Dayforce Hub

Led the iOS rebuild of Hub, the home surface of Dayforce — 3M+ iOS users, 1M+ daily active, 100% crash-free in production.

SwiftUISwift ConcurrencyWidgetKitApp IntentsAccessibilityTuist

The problem

Hub is the home surface of Dayforce — an enterprise HCM platform for payroll, time and attendance, and workforce management in regulated industries. On iOS it reaches 3M+ users, 1M+ of them daily. Yet Hub was buried behind a “More” tab and couldn’t host rich content: the app’s most valuable employee surface was effectively invisible, and the existing home screen surfaced the wrong information at the wrong time.

Constraints

  • Multi-tenant platform: one codebase serves hundreds of enterprise clients, each with different feature sets, localisation, and accessibility requirements
  • Regulated industries demand PII compliance and audit-ready data handling at every layer
  • Feature flags and A/B experimentation had to be first-class, not bolted on
  • Accessibility (VoiceOver, Dynamic Type, WCAG) was a hard requirement, not a nice-to-have
  • CI/CD via Fastlane and GitHub Actions — every commit had to be shippable

Approach

I led the iOS pod — 3 engineers including myself, within a wider Mobile HR team of 2 Android engineers, 2 QA, 1 backend engineer, and a PM — and owned Hub from architecture proposal through production rollout. We promoted Hub to the default landing tab and rebuilt it in SwiftUI as a multi-tab feed rendering rich text, images, video, action cards, and WidgetKit shortcuts.

Architecture — why Clean Architecture over MVVM alone. Plain MVVM let view models accumulate networking, caching, and tenant-configuration logic until they were hard to test and tightly coupled to the UIKit view controllers they replaced. We layered Clean Architecture over MVVM so domain logic — use cases and tenant rules — lived in plain Swift types with no UIKit or SwiftUI import. That made the rules unit-testable in isolation and reusable across both the widget and the main app.

Concurrency. Swift Concurrency (async/await + actors) throughout the data layer replaced a callback-pyramid networking stack and removed the data races that had caused intermittent state corruption under fast tab switching. We kept Combine where the legacy UIKit layer couldn’t be observed directly from SwiftUI — for example, exposing the shared session and feature-flag store as a publisher so UIKit and SwiftUI screens both reacted to flag changes without a forced reload.

Modularisation. Hub shipped as a self-contained Tuist module — its own targets, dependencies, and test suite — isolated from the monolith enough that a junior engineer can own a widget without touching the payroll engine.

WidgetKit + App Intents. We extended Hub onto the Home and Lock Screen, surfacing the most time-sensitive employee data — next shift, pending approvals, clock-in state — without unlocking the device. App Intents made those actions invocable from the widget and Siri, backed by the same domain use cases as the in-app path so behaviour and tenant rules stayed identical across surfaces. Timeline reloads were budgeted against WidgetKit’s tight memory ceiling rather than refreshed naively.

Localisation. I owned Hub’s localisation on iOS — authored source strings and managed handoff to the translation pipeline. Shipped in 39 languages at launch.

Accessibility. VoiceOver traversal order authored at design time, not patched post-review. Dynamic Type tested at every size; contrast ratios met in every theme variant.

Quality. XCTest + XCUITest coverage, crash reporting via Sentry, gated through Fastlane and GitHub Actions so every merge was shippable.

Outcome

  • Hub promoted from a buried “More” tab to the default landing surface of the iOS app
  • 3M+ iOS users, 1M+ daily active — the highest-trafficked surface in the Dayforce iOS app
  • 100% crash-free sessions since launch (Sentry), sustained across the rollout
  • 39 languages supported at launch
  • Adopted as the architectural reference implementation for subsequent Dayforce iOS features

Learnings

On a multi-tenant platform, a feature isn’t done when it works for one client — it’s done when it degrades gracefully across every tenant’s feature-flag and localisation combination. The failures that recurred weren’t crashes but layout and empty-state edge cases that surfaced only under a particular client’s flag matrix, so defensive defaults and a tenant-configuration test pass became part of the definition of done, not an afterthought.