Attendi Sample App

A sample app that implements an audio recording flow in SwiftUI: start, pause, and resume a recording while a timer tracks the elapsed time, then send the recording for (simulated) processing.

Context

This sample app was created for a technical interview for a senior Mobile (iOS + Android) engineer role at Attendi, a Dutch health-tech company that makes voice-driven reporting software for healthcare professionals, based on a given set of requirements.

Demo

▶️ Watch the sample (iOS) app in action!

Requirements

Requirement Version
Xcode 26 or later
Swift 6.3
Platforms iOS/macOS/visionOS 26

Project structure

The project separates the app shell from the feature code, which lives in a local Swift package:

Attendi/
├── Attendi.xcodeproj
├── Apps/
│   └── Attendi/                     # The app target (thin shell)
│       ├── AttendiApp.swift         # The @main entry point
│       ├── ContentView.swift        # The root view, hosting the Recording feature
│       └── Assets.xcassets
└── Packages/
    └── Features/                    # Local Swift package with the feature code
        ├── Package.swift
        ├── Sources/
        │   └── Recording/           # The Recording feature target
        │       ├── RecordingView.swift
        │       ├── RecordingViewModel.swift
        │       ├── RecordingButtonStyle.swift
        │       └── Images.xcassets  # Record, pause, and send icons
        └── Tests/
            ├── Recording/
            │   └── RecordingViewModelTests.swift
            └── Features.xctestplan

The app target only depends on the Features package and renders its public RecordingView view; all recording logic and UI live in the package's Recording target.

The Recording feature

State machine

The flow is modeled as a state machine in RecordingView.Model, an @Observable, @MainActor view model:

State Meaning
notRecording Idle; no recording in progress.
recording A recording is in progress and the timer is ticking.
paused The recording is paused; it can be resumed or sent.
processing The sent recording is being processed.

The main button starts, pauses, and resumes a recording. While paused, a send button appears; pressing it moves the flow into processing (currently simulated with a delay) before returning to idle.

Timer

While recording, an async task increments the elapsed seconds once per second — pausing stops it, resuming continues it, and a new recording resets it. The view formats the count as mm:ss and animates digit changes with a numeric text content transition.

Views

  • RecordingView — the public entry point of the feature. It renders the timer label and the control buttons for the model's current state, and forwards state changes back to the model.
  • RecordingButtonStyle — a custom ButtonStyle used by the controls: the label sits on a padded, circular red background, shrinks while pressed, and dims while disabled. The label and its padding scale with Dynamic Type via @ScaledMetric, and an invertStyle flag controls whether the label's color scheme is inverted for contrast.

Testing

The view model is covered by Swift Testing suites in RecordingViewModelTests.swift, grouped with nested @Suite types (initial state, button presses, computed properties, timer, and processing). The state-dependent behaviors are exercised with parameterized tests across all four states.

Run the tests with the Features scheme in Xcode (⌘U), or from the command line:

xcodebuild test -scheme Features -destination 'platform=iOS'

Tooling

  • .swift-format — configuration for Apple's swift-format, used by Xcode's built-in formatter (4-space indentation, 200-column lines).
Languages
Swift 100%