Phase 3 in full: the viewport gets gestures, the package moves to the Swift 6 language mode, and the adoption surface — documentation, decoder tests, visionOS — is filled in.
Read this before upgrading
Three changes are not drop-in:
- Swift 6.0 / Xcode 16 required.
Package.swiftnow usesswift-tools-version: 6.0, which Xcode 15 cannot parse. The language mode is still selectable (swiftLanguageModes: [.v6, .v5]); the toolchain floor is not. - Waveforms look different. Resampling defaults to peak pooling instead of mean. Bars gain contrast and keep their transients rather than eroding toward a uniform block as the bar count drops. Pass
resampleMode: .meanto restore the previous appearance. - Tap-to-seek resolves on touch-up, not touch-down, so the second tap of a double-tap can reset zoom instead of seeking. Scrubbing is unchanged — past the 4 pt threshold, seeks still fire continuously.
Zoom and pan
WaveformViewport shipped in 0.5.0 with complete coordinate math and no gestures. It is now driven by touch.
- New
WaveformZoomOptions:dragBehavior(.seek/.panWhenZoomed),maxZoomFactor,minVisibleDuration,resetsOnDoubleTap,yieldsToVerticalScroll, plus.disabled/.editor/.inScrollViewpresets. - Pinch zooms anchored at the gesture centroid, drag pans, double-tap resets.
.inScrollViewresolves the real conflict of a waveform row inside a list: vertical drags scroll, horizontal drags seek, taps still seek.- Gestures are inert without a
viewportbinding, so existing call sites are unaffected.
Swift 6
The package builds under the Swift 6 language mode with complete concurrency checking and zero warnings; CI fails on any new warning.
- Nonisolated-
deinitisolation fixed across all six player and recorder types. - Every polling
Timerbecame aTask. Besides the concurrency fix, playhead and amplitude updates no longer stall during scroll tracking — a default-modeTimerdoes not fire while a scroll view is tracking. - Audio-session notifications extract their Sendable values in the notification closure rather than sending
userInfoacross to the main actor.
Caches, decoding, docs
WaveformCachegains an LRU byte budget (Configuration,evictIfNeeded(),currentByteSize). It previously grew untilclear().ResampleCacheis now capacity-bounded — its key includes the visible slice, so a live pinch minted a permanent entry every frame.AudioSourceis a real code path viaWaveformLoader.load(source:)instead of an unreferenced public enum.AudioDecodergains end-to-end tests; it previously had none despite being the path every caller takes. Fixtures are synthesized at test time, so no binary assets enter the repo.- DocC catalog and
.spi.ymlfor documentation hosted on Swift Package Index — with noswift-docc-plugindependency, so the package stays dependency-free. - visionOS 1.0 added and verified in CI.
Not included
tvOS was evaluated and rejected. SwiftUI marks both DragGesture and MagnifyGesture unavailable on tvOS, so seeking, panning, marker taps, and double-tap cannot compile. A tvOS type-check reports DragGesture as the only error — everything else in the package is tvOS-clean — but the interaction path is not optional. Real support needs a focus-engine interaction model and is tracked for Phase 5.
129 tests passing. Full detail in CHANGELOG.md.