FDWaveformView renders an audio file into a UIView you can scrub and zoom
Reads an audio file and displays the waveform
At a glance
- What is it?
- FDWaveformView is a MIT licensed Swift package that draws an audio waveform into a UIView, with progress highlighting, zoomable rendering, built-in scrubbing, pinch to zoom and panning, and a delegate for load and render callbacks. It targets iOS 15 and visionOS 1, ships as a Swift package with an example app, and its README is unusually specific about the toolchain, including the fact that Xcode 26 cannot load its declared Swift tools version.
- Who is it for?
- FDWaveformView is the right dependency when a Swift app needs an audio scrubber and does not want to own the rendering code, because the waveform, the progress highlight, the zoom and the gestures are all handled by one view with a small and clearly bounded public API.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 9 days ago.
- What is it written in?
- Mainly Swift, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One view, one audio file, loaded asynchronously
FDWaveformView exists so that a Swift app can show users an audio waveform so they can preview audio, scrub, and pick positions with ease. It is a single open class, FDWaveformView, a subclass of UIView, and the usage is deliberately small. You add the view programmatically and then point it at a file, and the view loads and renders the audio in the background:
let thisBundle = Bundle(for: type(of: self))
let url = thisBundle.url(forResource: "Submarine", withExtension: "aiff")
self.waveform.audioURL = urlThe audioURL property is the entry point, and it is typed as an optional URL, so a file that is missing an extension needs the workaround the README points to for AVURLAsset without extensions. The view exposes whether the load is still in progress through loadingInProgress, and the sample count of the loaded asset through totalSamples, both read-only, which tells you whether the view is ready to be interacted with or whether you should wait for the delegate. Installation is through Swift Package Manager, added in Xcode through File, Add Package Dependencies, and the package is published under the MIT licence with a LICENSE file in the repository root.
Progress is a range of samples, not a percentage
The progress mechanism is a range, which is a good API decision and worth understanding. The highlightedSamples property takes an optional CountableRange of Int, and the samples in that range are tinted with progressColor while the rest of the waveform is drawn in wavesColor:
self.waveform.highlightedSamples = 0..<(self.waveform.totalSamples / 2)Because the unit is samples rather than a fraction of the duration, the same expression works for a file of any length and any sample rate, and it composes with the zoom range, which is also expressed in samples. A player that knows its current position in samples can set the highlight directly with no conversion. Since the property is settable and the view is a UIView, the usual UIKit animation machinery applies, and the README shows how to animate a change so the highlight slides rather than jumps:
UIView.animate(withDuration: 0.3) {
let randomNumber = arc4random() % self.waveform.totalSamples
self.waveform.highlightedSamples = 0 ..< randomNumber
}That is a small example, but it shows the intended use, a waveform that doubles as a progress bar, which is the pattern you want in a voice memo list or a podcast episode picker.
Zoom renders only the visible samples and adds detail progressively
The zoomSamples property is a CountableRange of Int that defines the portion of the file currently displayed, and the documented behaviour is to render only that portion while progressively adding detail as you zoom:
self.waveform.zoomSamples = 0..<(self.waveform.totalSamples / 4)This is the part of the design that makes the view usable with a long recording. A waveform of a three hour interview has an enormous number of samples, and drawing all of them into a few hundred points of screen width is both slow and uninformative, since a peak per pixel is all you can show anyway. Rendering the visible range and adding detail as the user zooms in means the view spends its effort on the part the user is looking at. The gesture that drives it is doesAllowStretch, which enables pinch to zoom, and the range in the zoomSamples property is the source of truth for what is on screen, so a host application that wants to implement its own zoom control can set the range directly and keep the gesture handling out of its way.
Three gesture switches, and one delegate for everything else
Interaction is three booleans rather than a gesture recogniser you have to build. doesAllowScrubbing enables tap and pan scrubbing, doesAllowStretch enables pinch to zoom, and doesAllowScroll enables panning across the waveform:
self.waveform.doesAllowScrubbing = true
self.waveform.doesAllowStretch = true
self.waveform.doesAllowScroll = trueEverything the host application needs to know arrives through the FDWaveformViewDelegate protocol, which is marked @objc public and whose methods are all optional. There are callbacks for the load cycle, waveformViewWillLoad and waveformViewDidLoad, for the render cycle, waveformViewWillRender and waveformViewDidRender, and for interaction, waveformDidBeginPanning, waveformDidEndPanning and waveformDidEndScrubbing. That last one is the one an audio editor wants, because it is the moment to read the selected position. The class also implements UIGestureRecognizerDelegate, including the method that decides whether recognisers should be recognised simultaneously, and the README is explicit that a couple of exposed things are not considered public API, so the public surface is the one documented as the complete API for the module.
Antialiasing and re-rendering on layout changes
Two rendering details are called out under rendering quality, and both are the kind of thing that separates a smooth waveform from a jagged one. The first is that antialiased waveforms draw extra pixels to avoid jagged edges, which matters because a waveform is a dense forest of vertical strokes and the eye is very good at spotting aliasing on them. The second is that autolayout-driven size changes trigger re-rendering to prevent pixelation, which is the difference between a view that stays crisp when its frame changes and one that scales a stale bitmap. The colour model is also explicit rather than inherited, with wavesColor as the tint for the base waveform image and progressColor for the highlighted waveform, both open variables that can be changed after creation. The whole set of public properties is small enough to read in one screen, the class, the delegate protocol with its seven optional callbacks, and the handful of non-public things the project chooses not to promise.
The toolchain constraint is stated in the README
The rendering quality section ends with a platform statement that is easy to miss. The package supports iOS 15 and above, visionOS 1 and above, and Swift tools version 6.4, as declared in Package.swift, and it adds that Xcode 26 cannot load that tools version. In other words, the package will not build on the Xcode generation immediately before the current one, and if your CI image is one release behind, that is a real blocker rather than a warning. The version support also explains the visionOS entry, which arrived with the 5.1.1 release titled Add visionOS support on 2025-12-07, alongside 5.1.0 titled Separate out data source, so the data source is now a separate component from the view. The current release is 5.1.2, published on 2026-10-15, and the last push was on the same day, so the project is being worked on. The test story is described in the same practical register, with a CI badge and a lint badge, and unit tests that run on GitHub Actions.
Running the tests is an Xcode destination problem
The testing section is the most specific part of the README, and it exists because the obvious command does not work. GitHub Actions runs the suite on the Xcode 27 runner image, whose default Xcode is 27.0 and which provides an iPhone 17 simulator on iOS 27.0, and the command is:
xcodebuild test -scheme FDWaveformView -destination 'platform=iOS Simulator,name=iPhone 17,OS=27.0'On another Xcode you have to list the simulators that copy of Xcode can see and pass one of those identifiers, because xcodebuild only accepts destinations from the selected Xcode, so an identifier printed by a different installation will fail:
xcrun simctl list devices available | grep iPhone
xcodebuild test -scheme FDWaveformView -destination 'id=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'The example app target deploys to iOS 18.6, so its simulator has to be at least that version. And there is a warning worth knowing before you try the obvious thing, that xcrun swift test builds the package for macOS, and since these sources import UIKit that command does not compile this package. Releases are automated with Release Please, which opens a release pull request from fix, feat and BREAKING CHANGE commit messages, and merging it runs the release workflow that tests the package and publishes the GitHub Release. The tags deliberately carry no v prefix, and the release does not attach a Linux static library, again because of the UIKit import.
Editorial conclusion
FDWaveformView is the right dependency when a Swift app needs an audio scrubber and does not want to own the rendering code, because the waveform, the progress highlight, the zoom and the gestures are all handled by one view with a small and clearly bounded public API. It is the wrong choice for a macOS or Linux command line tool, since the sources import UIKit, and it is the wrong choice if you need it to build with a toolchain older than Swift 6.4, because the declared tools version excludes Xcode 26. Before adopting it, check that the Xcode you use can load the package, plan the CI runner accordingly, since the documented test command targets the Xcode 27 image with an iPhone 17 simulator on iOS 27.0, and read the delegate methods rather than assuming the load is synchronous, because the audio is rendered asynchronously.
Frequently asked questions
How do I load an audio file into FDWaveformView?
Add the view programmatically and set its audioURL property, for example self.waveform.audioURL = url, where the URL points at the file in your bundle. The audio is loaded and rendered asynchronously, and loadingInProgress tells you whether that has finished.
How do I show playback progress in a waveform?
Set the highlightedSamples property to a range of sample indices, which are tinted with progressColor while the rest is drawn in wavesColor. The range can be animated with UIView.animate so the highlight slides rather than jumps.
Which platforms and toolchain does FDWaveformView support?
The README states iOS 15 and above, visionOS 1 and above, and Swift tools version 6.4 as declared in Package.swift, and notes that Xcode 26 cannot load that tools version. visionOS support was added in release 5.1.1 on 2025-12-07 and the current release is 5.1.2.
Why does xcrun swift test fail for this package?
That command builds the package for macOS, and the sources import UIKit, so it does not compile. The documented command is xcodebuild test with a simulator destination from the same Xcode installation, since xcodebuild only accepts destinations from the selected Xcode.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/fulldecent-fdwaveformview)Was this useful?