Waavform
Open-source SwiftUI audio component I designed and built. One line to use, every pixel overridable.
- Client
- Open source
- Role
- Design and engineering
- Year
- 2024

Context
Waavform is a waveform-based audio player component for SwiftUI, published as an open-source Swift package. Drop it into an iOS project, point it at an audio file, and you get a player that draws the waveform, tracks position, and handles transport.
It’s a small project, a component rather than a product. I’m including it because the design problem in a component is different from the one in an app, and it’s the clearest example of my work where the interface and the API are the same decision.
The problem
SwiftUI ships no waveform player. Anyone who needs one is choosing between
AVPlayer with a plain progress bar, which tells you nothing about the audio, or
building waveform rendering, hit-testing and playback sync themselves, which is
days of work for what most apps treat as a minor UI element.
The component design problem underneath: a player like this has to be usable in one line by someone who wants a sensible default, and completely overridable by someone who has a design system to match. Those pull in opposite directions. Aim at the first and you build something nobody can fit into a real app; aim at the second and the simplest possible use requires twenty lines of configuration.
What I built
One line is the whole API.
Waavform(audio: "TheMoon", type: "mp3")
That renders a working player with waveform, transport, timecode and view switching. Nothing else is required. Every other parameter is optional and named, so the component gets more configurable the further in you go rather than demanding decisions up front. The default is the product, and customisation is opt-in.
The waveform is the scrubber. There’s no separate progress bar. Played audio is filled in the progress colour, unplayed audio sits in the background colour, and the boundary between them is the playhead. One element is the display, the position indicator and the control. It also means the thing you’re scrubbing through shows you where you’re going. You can see the quiet part coming.
Time labels ride the playhead. Current time sits on the playhead and moves with it; duration pins to the end of the waveform. Most players park a timecode row underneath, which makes you look in two places. Attaching the number to the thing it describes costs nothing and removes a lookup.

Playback in the linear view. The filled region and the time label are the same mechanism. There is no separate progress indicator to keep in sync.
Two views, because there are two questions. Linear draws the whole track at once: how far through am I, where are the loud parts, what’s the shape of this thing. Scroll fixes the playhead and moves the waveform past it at higher magnification: what’s happening right now. Neither is the better default, so it’s a toggle, and the toggle is a text link labelled with its destination (“Scroll” when you’re in linear), so you never have to work out which state you’re in.

It can also be a rendering primitive. showTransport and showScroll hide
the built-in chrome, and play(), pause(), stop() and toggleView() are
exposed, so a host app can draw its own controls and drive the waveform directly.
The same component serves someone who wants a finished widget and someone who
only wants the hard part.
Technology
- Swift and SwiftUI, distributed as a Swift Package Manager package for iOS
- The public surface is one view with defaulted parameters, so a single line gets a working player and every visual decision stays overridable
Where it landed
Published as a Swift package in January 2024. Small numbers, a couple of dozen stars and a handful of forks, which is what a focused component in a narrow niche looks like. I’d rather say that than dress it up.