@@ -11,6 +11,8 @@ public struct WaveformView<Content: View>: View {
11
11
private let content : ( WaveformShape ) -> Content
12
12
13
13
@State private var samples : [ Float ] = [ ]
14
+ @State private var rescaleTimer : Timer ?
15
+ @State private var currentSize : CGSize = . zero
14
16
15
17
/**
16
18
Creates a new WaveformView which displays a waveform for the audio at `audioURL`.
@@ -39,26 +41,54 @@ public struct WaveformView<Content: View>: View {
39
41
public var body : some View {
40
42
GeometryReader { geometry in
41
43
content ( WaveformShape ( samples: samples, configuration: configuration, renderer: renderer) )
44
+ . scaleEffect ( x: scaleDuringResize ( for: geometry) , y: 1 , anchor: . trailing)
42
45
. onAppear {
43
46
guard samples. isEmpty else { return }
44
47
update ( size: geometry. size, url: audioURL, configuration: configuration)
45
48
}
46
- . modifier ( OnChange ( of: geometry. size, action: { newValue in update ( size: newValue, url: audioURL, configuration: configuration) } ) )
49
+ . modifier ( OnChange ( of: geometry. size, action: { newValue in update ( size: newValue, url: audioURL, configuration: configuration, delayed : true ) } ) )
47
50
. modifier ( OnChange ( of: audioURL, action: { newValue in update ( size: geometry. size, url: audioURL, configuration: configuration) } ) )
48
51
. modifier ( OnChange ( of: configuration, action: { newValue in update ( size: geometry. size, url: audioURL, configuration: newValue) } ) )
49
52
}
50
53
}
51
54
52
- private func update( size: CGSize , url: URL , configuration: Waveform . Configuration ) {
53
- Task ( priority: priority) {
54
- do {
55
- let samplesNeeded = Int ( size. width * configuration. scale)
56
- let samples = try await WaveformAnalyzer ( ) . samples ( fromAudioAt: url, count: samplesNeeded)
57
- await MainActor . run { self . samples = samples }
58
- } catch {
59
- assertionFailure ( error. localizedDescription)
55
+ private func update( size: CGSize , url: URL , configuration: Waveform . Configuration , delayed: Bool = false ) {
56
+ rescaleTimer? . invalidate ( )
57
+
58
+ let updateTask : @Sendable ( Timer ? ) -> Void = { _ in
59
+ Task ( priority: . userInitiated) {
60
+ do {
61
+ let samplesNeeded = Int ( size. width * configuration. scale)
62
+ let samples = try await WaveformAnalyzer ( ) . samples ( fromAudioAt: url, count: samplesNeeded)
63
+
64
+ await MainActor . run {
65
+ self . currentSize = size
66
+ self . samples = samples
67
+ }
68
+ } catch {
69
+ assertionFailure ( error. localizedDescription)
70
+ }
60
71
}
61
72
}
73
+
74
+ if delayed {
75
+ rescaleTimer = Timer . scheduledTimer ( withTimeInterval: 0.05 , repeats: false , block: updateTask)
76
+ RunLoop . main. add ( rescaleTimer!, forMode: . common)
77
+ } else {
78
+ updateTask ( nil )
79
+ }
80
+ }
81
+
82
+ /*
83
+ * During resizing, we only visually scale the shape to make it look more seamless,
84
+ * before we re-calculate the pixel-perfect re-sampled waveform, which is costly.
85
+ * Due to the complex way we need to render the actual waveform based on samples
86
+ * available and size to occupy, the re-scaling currently only supports enlarging.
87
+ * If we resize to a smaller size, the waveform simply overflows.
88
+ */
89
+ private func scaleDuringResize( for geometry: GeometryProxy ) -> CGFloat {
90
+ guard currentSize != . zero else { return 1 }
91
+ return max ( geometry. size. width / currentSize. width, 1 )
62
92
}
63
93
}
64
94
0 commit comments