@@ -3,68 +3,61 @@ import DSWaveformImageViews
33import SwiftUI
44
55struct SwiftUIExampleView : View {
6- private enum ActiveTab : Hashable {
7- case recorder, shape, overview
6+ private enum Tab : Hashable {
7+ case gallery
8+ case recorder
89 }
910
10- private static let colors = [ UIColor . systemPink, UIColor . systemBlue, UIColor . systemGreen]
11- private static var randomColor : UIColor { colors. randomElement ( ) ! }
12-
13- private static var audioURLs : [ URL ? ] = [
14- Bundle . main. url ( forResource: " example_sound " , withExtension: " m4a " ) ,
15- Bundle . main. url ( forResource: " example_sound_2 " , withExtension: " m4a " )
16- ]
17- private static func randomURL( _ current: URL ? ) -> URL ? { audioURLs. filter { $0 != current } . randomElement ( ) ! }
18-
19- @StateObject private var audioRecorder : AudioRecorder = AudioRecorder ( )
20-
21- @State private var configuration : Waveform . Configuration = Waveform . Configuration (
22- style: . striped( Waveform . Style. StripeConfig ( color: Self . randomColor, width: 3 , lineCap: . round) ) ,
23- verticalScalingFactor: 0.9
24- )
25-
26- @State private var liveConfiguration : Waveform . Configuration = Waveform . Configuration (
27- style: . striped( . init( color: randomColor, width: 3 , spacing: 3 ) )
28- )
29-
30- @State private var audioURL : URL ? = audioURLs. first!
31- @State private var samples : [ Float ] = [ ]
32- @State private var silence : Bool = true
33- @State private var selection : ActiveTab = . overview
11+ @State private var tab : Tab = . gallery
3412
3513 var body : some View {
36- VStack {
37- Text ( " SwiftUI examples " )
38- . font ( . largeTitle. bold ( ) )
39-
40- Picker ( " Hey " , selection: $selection) {
41- Text ( " Recorder " ) . tag ( ActiveTab . recorder)
42- Text ( " Shape " ) . tag ( ActiveTab . shape)
43- Text ( " Overview " ) . tag ( ActiveTab . overview)
14+ VStack ( spacing: 0 ) {
15+ Picker ( " Section " , selection: $tab) {
16+ Label ( " Gallery " , systemImage: " rectangle.grid.2x2 " ) . tag ( Tab . gallery)
17+ Label ( " Live " , systemImage: " mic.circle.fill " ) . tag ( Tab . recorder)
4418 }
4519 . pickerStyle ( . segmented)
20+ . labelsHidden ( )
4621 . padding ( . horizontal)
22+ . padding ( . top, 12 )
4723
48- switch selection {
49- case . recorder: recordingExample
50- case . shape: shape
51- case . overview: overview
24+ switch tab {
25+ case . gallery: WaveformGalleryView ( )
26+ case . recorder: LiveRecordingTab ( )
5227 }
5328 }
54- . padding ( . vertical, 20 )
5529 }
30+ }
31+
32+ private struct LiveRecordingTab : View {
33+ @StateObject private var audioRecorder = AudioRecorder ( )
34+ @State private var silence : Bool = true
35+ @State private var configuration : Waveform . Configuration = Waveform . Configuration (
36+ style: . striped( . init( color: . systemIndigo, width: 3 , spacing: 3 ) )
37+ )
38+
39+ var body : some View {
40+ VStack ( spacing: 16 ) {
41+ VStack ( alignment: . leading, spacing: 4 ) {
42+ Text ( " Live recording " )
43+ . font ( . title2. weight ( . semibold) )
44+ Text ( " WaveformLiveCanvas streams microphone amplitude into a circular renderer. " )
45+ . font ( . callout)
46+ . foregroundStyle ( . secondary)
47+ }
48+ . frame ( maxWidth: . infinity, alignment: . leading)
49+ . padding ( . horizontal)
50+ . padding ( . top, 16 )
5651
57- @ViewBuilder
58- private var recordingExample : some View {
59- VStack {
6052 WaveformLiveCanvas (
6153 samples: audioRecorder. samples,
62- configuration: liveConfiguration ,
54+ configuration: configuration ,
6355 renderer: CircularWaveformRenderer ( kind: . circle) ,
6456 shouldDrawSilencePadding: silence
6557 )
58+ . padding ( . horizontal)
6659
67- Toggle ( " draw silence" , isOn: $silence)
60+ Toggle ( " Pad silence" , isOn: $silence)
6861 . controlSize ( . mini)
6962 . padding ( . horizontal)
7063
@@ -75,131 +68,8 @@ struct SwiftUIExampleView: View {
7568 isRecording: $audioRecorder. isRecording
7669 )
7770 . padding ( . horizontal)
78- }
79- }
80-
81- @ViewBuilder
82- private var shape : some View {
83- VStack {
84- Text ( " WaveformView " ) . font ( . monospaced( . title. bold ( ) ) ( ) )
8571
86- HStack {
87- Button {
88- configuration = configuration. with ( style: . striped( Waveform . Style. StripeConfig ( color: Self . randomColor, width: 3 , lineCap: . round) ) )
89- liveConfiguration = liveConfiguration. with ( style: . striped( . init( color: Self . randomColor, width: 3 , spacing: 3 ) ) )
90- } label: {
91- Label ( " color " , systemImage: " dice " )
92- . frame ( maxWidth: . infinity)
93- }
94- . font ( . body. bold ( ) )
95- . padding ( 8 )
96- . background ( Color ( . systemGray6) )
97- . cornerRadius ( 10 )
98-
99- Button {
100- audioURL = Self . randomURL ( audioURL)
101- print ( " will draw \( audioURL!) " )
102- } label: {
103- Label ( " waveform " , systemImage: " dice " )
104- . frame ( maxWidth: . infinity)
105- }
106- . font ( . body. bold ( ) )
107- . padding ( 8 )
108- . background ( Color ( . systemGray6) )
109- . cornerRadius ( 10 )
110- }
111- . padding ( . horizontal)
112-
113- // the if let is left here intentionally to illustrate how to deal with optional URLs
114- // as this was asked in an older GitHub issue
115- if let audioURL {
116- WaveformView ( audioURL: audioURL, configuration: configuration)
117-
118- WaveformView (
119- audioURL: audioURL,
120- configuration: configuration,
121- renderer: CircularWaveformRenderer ( kind: . ring( 0.7 ) )
122- ) { shape in
123- // you may completely override the shape styling this way
124- shape
125- . stroke (
126- LinearGradient ( colors: [ . red, Color ( Self . randomColor) ] , startPoint: . zero, endPoint: . topTrailing) ,
127- style: StrokeStyle ( lineWidth: 3 , lineCap: . round) )
128- }
129-
130- Divider ( )
131- Text ( " WaveformShape " ) . font ( . monospaced( . title. bold ( ) ) ( ) )
132-
133- /// **Note:** It's possible, but discouraged to use WaveformShape directly.
134- /// As Shapes should not do any expensive computations, the analyzing should happen outside,
135- /// hence making the API a tiny bit clumsy if used directly, since we do require to know the size,
136- /// even though the Shape of course intrinsically knows its size already.
137- GeometryReader { geometry in
138- WaveformShape ( samples: samples)
139- . fill ( Color . orange)
140- . task {
141- do {
142- let samplesNeeded = Int ( geometry. size. width * configuration. scale)
143- let samples = try await WaveformAnalyzer ( ) . samples ( fromAudioAt: audioURL, count: samplesNeeded)
144- await MainActor . run { self . samples = samples }
145- } catch {
146- assertionFailure ( error. localizedDescription)
147- }
148- }
149- }
150- }
151- }
152- }
153-
154- @ViewBuilder
155- private var overview : some View {
156- if let audioURL {
157- HStack {
158- VStack {
159- WaveformView ( audioURL: audioURL, configuration: . init( style: . filled( . red) ) )
160- WaveformView ( audioURL: audioURL, configuration: . init( style: . outlined( . blue, 0.5 ) ) )
161- WaveformView ( audioURL: audioURL, configuration: . init( style: . gradient( [ . yellow, . orange] ) ) )
162- WaveformView ( audioURL: audioURL, configuration: . init( style: . gradientOutlined( [ . yellow, . orange] , 1 ) ) )
163- WaveformView ( audioURL: audioURL, configuration: . init( style: . striped( . init( color: . red, width: 2 , spacing: 1 ) ) ) )
164-
165- WaveformView ( audioURL: audioURL, configuration: . init( style: . striped( . init( color: . black) ) ) ) { shape in
166- shape // override the shape styling
167- . stroke ( LinearGradient ( colors: [ . blue, . pink] , startPoint: . bottom, endPoint: . top) , lineWidth: 3 )
168- } placeholder: {
169- ProgressView ( )
170- }
171- }
172-
173- VStack {
174- WaveformView ( audioURL: audioURL, configuration: . init( style: . filled( . red) ) , renderer: CircularWaveformRenderer ( ) )
175- WaveformView ( audioURL: audioURL, configuration: . init( style: . outlined( . blue, 0.5 ) ) , renderer: CircularWaveformRenderer ( ) )
176- WaveformView ( audioURL: audioURL, configuration: . init( style: . gradient( [ . yellow, . orange] ) ) , renderer: CircularWaveformRenderer ( ) )
177- WaveformView ( audioURL: audioURL, configuration: . init( style: . gradientOutlined( [ . yellow, . orange] , 1 ) ) , renderer: CircularWaveformRenderer ( ) )
178- WaveformView ( audioURL: audioURL, configuration: . init( style: . striped( . init( color: . red, width: 2 , spacing: 2 ) ) ) , renderer: CircularWaveformRenderer ( ) )
179-
180- WaveformView ( audioURL: audioURL, configuration: . init( style: . striped( . init( color: . black) ) ) , renderer: CircularWaveformRenderer ( ) ) { shape in
181- shape // override the shape styling
182- . stroke ( LinearGradient ( colors: [ . blue, . pink] , startPoint: . bottom, endPoint: . top) , lineWidth: 3 )
183- } placeholder: {
184- ProgressView ( )
185- }
186- }
187-
188- VStack {
189- WaveformView ( audioURL: audioURL, configuration: . init( style: . filled( . red) ) , renderer: CircularWaveformRenderer ( kind: . ring( 0.5 ) ) )
190- WaveformView ( audioURL: audioURL, configuration: . init( style: . outlined( . blue, 0.5 ) ) , renderer: CircularWaveformRenderer ( kind: . ring( 0.5 ) ) )
191- WaveformView ( audioURL: audioURL, configuration: . init( style: . gradient( [ . yellow, . orange] ) ) , renderer: CircularWaveformRenderer ( kind: . ring( 0.5 ) ) )
192- WaveformView ( audioURL: audioURL, configuration: . init( style: . gradientOutlined( [ . yellow, . orange] , 1 ) ) , renderer: CircularWaveformRenderer ( kind: . ring( 0.5 ) ) )
193- WaveformView ( audioURL: audioURL, configuration: . init( style: . striped( . init( color: . red, width: 2 , spacing: 2 ) ) ) , renderer: CircularWaveformRenderer ( kind: . ring( 0.5 ) ) )
194-
195- WaveformView ( audioURL: audioURL, configuration: . init( style: . striped( . init( color: . black) ) ) , renderer: CircularWaveformRenderer ( kind: . ring( 0.5 ) ) ) { shape in
196- shape // override the shape styling
197- . stroke ( LinearGradient ( colors: [ . blue, . pink] , startPoint: . bottom, endPoint: . top) , lineWidth: 3 )
198- } placeholder: {
199- ProgressView ( )
200- }
201- }
202- }
72+ Spacer ( minLength: 0 )
20373 }
20474 }
20575}
@@ -224,9 +94,7 @@ private class AudioRecorder: NSObject, ObservableObject, RecordingDelegate {
22494
22595 override init ( ) {
22696 audioManager = SCAudioManager ( )
227-
22897 super. init ( )
229-
23098 audioManager. prepareAudioRecording ( )
23199 audioManager. recordingDelegate = self
232100 }
@@ -245,16 +113,13 @@ private class AudioRecorder: NSObject, ObservableObject, RecordingDelegate {
245113 // MARK: - RecordingDelegate
246114
247115 func audioManager( _ manager: SCAudioManager ! , didAllowRecording flag: Bool ) { }
248-
249116 func audioManager( _ manager: SCAudioManager ! , didFinishRecordingSuccessfully flag: Bool ) { }
250117
251118 func audioManager( _ manager: SCAudioManager ! , didUpdateRecordProgress progress: CGFloat ) {
252119 let linear = 1 - pow( 10 , manager. lastAveragePower ( ) / 20 )
253-
254120 // Here we add the same sample 3 times to speed up the animation.
255121 // Usually you'd just add the sample once.
256122 recordingTime = audioManager. currentRecordingTime
257123 samples += [ linear, linear, linear]
258124 }
259125}
260-
0 commit comments