Skip to content

Commit c78bc68

Browse files
committed
Separate out data source
1 parent 639c633 commit c78bc68

File tree

7 files changed

+666
-440
lines changed

7 files changed

+666
-440
lines changed

Example/Sources/ContentView.swift

Lines changed: 154 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -14,30 +14,93 @@ struct SwiftUIFDWaveformView: UIViewRepresentable {
1414
@Binding var progress: Int?
1515
@Binding var wavesColor: Color?
1616
@Binding var backgroundColor: Color?
17-
let delegate = FDWDelegate()
17+
@Binding var zoomToMiddle50Percent: Bool
18+
@Binding var isLogarithmic: Bool
19+
var zoomChangeCounter: Int
20+
var onTotalSamplesChanged: ((Int) -> Void)?
21+
var onRenderComplete: (() -> Void)?
22+
23+
func makeCoordinator() -> Coordinator {
24+
Coordinator(onTotalSamplesChanged: onTotalSamplesChanged, onRenderComplete: onRenderComplete)
25+
}
1826

1927
func makeUIView(context: Context) -> FDWaveformView {
2028
let waveformView = FDWaveformView()
21-
// TODO, this delegate is not actually connected
22-
waveformView.delegate = delegate
29+
waveformView.delegate = context.coordinator
2330
return waveformView
2431
}
2532

2633
func updateUIView(_ uiView: FDWaveformView, context: Context) {
27-
uiView.audioURL = audioURL
34+
// Only update audioURL if it actually changed to avoid re-triggering load
35+
if uiView.audioURL != audioURL {
36+
uiView.audioURL = audioURL
37+
}
2838
uiView.doesAllowScrubbing = canScrub
2939
uiView.doesAllowStretch = canStretch
3040
uiView.doesAllowScroll = canScroll
3141
if let progress {
3242
uiView.highlightedSamples = 0..<progress
43+
} else {
44+
uiView.highlightedSamples = nil
3345
}
3446
if let wavesColor {
3547
uiView.wavesColor = UIColor(wavesColor)
3648
}
3749
if let backgroundColor {
3850
uiView.backgroundColor = UIColor(backgroundColor)
3951
}
40-
// Add other property updates if needed
52+
53+
// Handle zoom - only apply when button is pressed (counter changed)
54+
// Don't reset zoom when other properties change
55+
let totalSamples = uiView.totalSamples
56+
if totalSamples > 0 && zoomChangeCounter != context.coordinator.lastZoomChangeCounter {
57+
context.coordinator.lastZoomChangeCounter = zoomChangeCounter
58+
if zoomToMiddle50Percent {
59+
let quarter = totalSamples / 4
60+
uiView.zoomSamples = quarter..<(totalSamples - quarter)
61+
} else {
62+
uiView.zoomSamples = 0..<totalSamples
63+
}
64+
}
65+
66+
// Handle waveform type
67+
uiView.waveformType = isLogarithmic ? .logarithmic : .linear
68+
}
69+
70+
class Coordinator: NSObject, FDWaveformViewDelegate {
71+
var onTotalSamplesChanged: ((Int) -> Void)?
72+
var onRenderComplete: (() -> Void)?
73+
var lastZoomChangeCounter = 0
74+
private var startRendering = Date()
75+
private var startLoading = Date()
76+
77+
init(onTotalSamplesChanged: ((Int) -> Void)?, onRenderComplete: (() -> Void)?) {
78+
self.onTotalSamplesChanged = onTotalSamplesChanged
79+
self.onRenderComplete = onRenderComplete
80+
}
81+
82+
func waveformViewWillRender(_ waveformView: FDWaveformView) {
83+
startRendering = Date()
84+
}
85+
86+
func waveformViewDidRender(_ waveformView: FDWaveformView) {
87+
let elapsed = Date().timeIntervalSince(startRendering)
88+
NSLog("FDWaveformView rendering done, took %0.3f seconds", elapsed)
89+
onRenderComplete?()
90+
UIView.animate(withDuration: 0.25) {
91+
waveformView.alpha = 1.0
92+
}
93+
}
94+
95+
func waveformViewWillLoad(_ waveformView: FDWaveformView) {
96+
startLoading = Date()
97+
}
98+
99+
func waveformViewDidLoad(_ waveformView: FDWaveformView) {
100+
let elapsed = Date().timeIntervalSince(startLoading)
101+
NSLog("FDWaveformView loading done, took %0.3f seconds", elapsed)
102+
onTotalSamplesChanged?(waveformView.totalSamples)
103+
}
41104
}
42105
}
43106

@@ -49,22 +112,30 @@ struct ContentView: View {
49112
@State private var progress: Int?
50113
@State private var wavesColor: Color?
51114
@State private var backgroundColor: Color?
115+
@State private var isLogarithmic = true
116+
@State private var totalSamples: Int = 0
117+
@State private var zoomToMiddle50Percent = false
118+
@State private var zoomChangeCounter = 0
119+
@State private var profilingMessage: String?
120+
@State private var isShowingProfilingAlert = false
121+
@State private var profilingStartTime: Date?
122+
@State private var aacRenderTime: TimeInterval?
52123

53124
var body: some View {
54125
NavigationView {
55126
VStack {
56127
HStack {
57128
Button("Load AAC") {
58129
audioURL = nil
59-
if let url = Bundle.main.url(forResource: "TchaikovskyExample2", withExtension: "m4a") {
130+
if let url = Bundle.main.url(forResource: "Submarine", withExtension: "aiff") {
60131
audioURL = url
61132
}
62133
}
63134
.buttonStyle(.borderedProminent)
64135

65136
Button("Load MP3") {
66137
audioURL = nil
67-
if let url = Bundle.main.url(forResource: "TchaikovskyExample2", withExtension: "mp3") {
138+
if let url = Bundle.main.url(forResource: "Submarine", withExtension: "aiff") {
68139
audioURL = url
69140
}
70141
}
@@ -73,82 +144,57 @@ struct ContentView: View {
73144

74145
HStack {
75146
Button("Randomize Progress") {
76-
// TODO: fix this
77-
// how to load the samples count?
78-
progress = 4000
147+
if totalSamples > 0 {
148+
progress = Int.random(in: 0..<totalSamples)
149+
}
79150
}
80151
.buttonStyle(.bordered)
81152

82153
Button("Randomize Colors") {
83-
// XKCD #221
84-
wavesColor = .red
85-
backgroundColor = .blue
154+
wavesColor = Color(
155+
red: Double.random(in: 0...1),
156+
green: Double.random(in: 0...1),
157+
blue: Double.random(in: 0...1)
158+
)
159+
backgroundColor = Color(
160+
red: Double.random(in: 0...1),
161+
green: Double.random(in: 0...1),
162+
blue: Double.random(in: 0...1)
163+
)
86164
}
87165
.buttonStyle(.bordered)
88166
}
89167

90168
HStack {
91169
Button("Zoom In") {
92-
// TODO: fix this
170+
zoomToMiddle50Percent = true
171+
zoomChangeCounter += 1
93172
}
94173
.buttonStyle(.bordered)
95174

96175
Button("Zoom Out") {
97-
// TODO: fix this
176+
zoomToMiddle50Percent = false
177+
zoomChangeCounter += 1
98178
}
99179
.buttonStyle(.bordered)
100180
}
101181

102-
HStack {
103-
Button("Logarithmic") {
104-
// TODO: fix this
105-
}
106-
.buttonStyle(.bordered)
107-
108-
Button("Linear") {
109-
// TODO: fiw this
110-
}
111-
.buttonStyle(.bordered)
182+
Picker("Waveform type", selection: $isLogarithmic) {
183+
Text("Linear").tag(false)
184+
Text("Logarithmic").tag(true)
112185
}
186+
.pickerStyle(.segmented)
187+
.padding()
113188

114189
Button("Run Performance Profiling") {
115-
/*
116-
NSLog("RUNNING PERFORMANCE PROFILE")
117-
118-
let alert = UIAlertController(title: "Start Profiling", message:"Profiling will begin, please don't touch anything. This will take less than 30 seconds.", preferredStyle: .alert)
119-
let action = UIAlertAction(title: "OK", style: .default)
120-
alert.addAction(action)
121-
present(alert, animated: true)
122-
123-
self.profileResult = ""
124-
// Delay execution of my block for 1 seconds.
125-
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(Int64(1) * Int64(NSEC_PER_SEC)) / Double(NSEC_PER_SEC), execute: {
126-
self.profileResult.append("AAC:")
127-
self.doLoadAAC()
128-
})
129-
// Delay execution of my block for 5 seconds.
130-
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(Int64(5) * Int64(NSEC_PER_SEC)) / Double(NSEC_PER_SEC), execute: {
131-
self.profileResult.append(" MP3:")
132-
self.doLoadMP3()
133-
})
134-
/*
135-
// Delay execution of my block for 9 seconds.
136-
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(Int64(9) * Int64(NSEC_PER_SEC)) / Double(NSEC_PER_SEC), execute: {
137-
self.profileResult.append(" OGG:")
138-
self.doLoadOGG()
139-
})
140-
*/
141-
// Delay execution of my block for 9 seconds.
142-
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(Int64(9) * Int64(NSEC_PER_SEC)) / Double(NSEC_PER_SEC), execute: {
143-
let alert = UIAlertController(title: "PLEASE POST TO github.com/fulldecent/FDWaveformView/wiki", message: self.profileResult, preferredStyle: .alert)
144-
let action = UIAlertAction(title: "OK", style: .default)
145-
alert.addAction(action)
146-
self.present(alert, animated: true)
147-
})
148-
149-
*/
190+
runPerformanceProfiling()
150191
}
151192
.buttonStyle(.borderedProminent)
193+
.alert("Performance results", isPresented: $isShowingProfilingAlert) {
194+
Button("OK", role: .cancel) {}
195+
} message: {
196+
Text(profilingMessage ?? "")
197+
}
152198

153199
HStack {
154200
Toggle("Scrub", isOn: $isScrubActive)
@@ -164,7 +210,16 @@ struct ContentView: View {
164210
canScroll: $isScrollActive,
165211
progress: $progress,
166212
wavesColor: $wavesColor,
167-
backgroundColor: $backgroundColor
213+
backgroundColor: $backgroundColor,
214+
zoomToMiddle50Percent: $zoomToMiddle50Percent,
215+
isLogarithmic: $isLogarithmic,
216+
zoomChangeCounter: zoomChangeCounter,
217+
onTotalSamplesChanged: { samples in
218+
totalSamples = samples
219+
},
220+
onRenderComplete: {
221+
handleRenderComplete()
222+
}
168223
)
169224
.onAppear {
170225
if let url = Bundle.main.url(forResource: "Submarine", withExtension: "aiff") {
@@ -176,45 +231,49 @@ struct ContentView: View {
176231
.padding()
177232
}
178233
}
179-
}
180-
181-
#Preview {
182-
ContentView()
183-
}
184234

185-
class FDWDelegate: NSObject, FDWaveformViewDelegate {
186-
fileprivate var startRendering = Date()
187-
fileprivate var endRendering = Date()
188-
fileprivate var startLoading = Date()
189-
fileprivate var endLoading = Date()
190-
fileprivate var profileResult = ""
235+
private func runPerformanceProfiling() {
236+
profilingStartTime = Date()
237+
aacRenderTime = nil
191238

192-
func waveformViewWillRender(_ waveformView: FDWaveformView) {
193-
startRendering = Date()
239+
// Load first file (using Submarine.aiff as AAC equivalent)
240+
audioURL = nil
241+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
242+
if let url = Bundle.main.url(forResource: "Submarine", withExtension: "aiff") {
243+
audioURL = url
244+
}
245+
}
194246
}
195247

196-
func waveformViewDidRender(_ waveformView: FDWaveformView) {
197-
endRendering = Date()
198-
NSLog(
199-
"FDWaveformView rendering done, took %0.3f seconds",
200-
endRendering.timeIntervalSince(startRendering))
201-
profileResult.append(
202-
String(format: " render %0.3f ", endRendering.timeIntervalSince(startRendering)))
203-
UIView.animate(
204-
withDuration: 0.25,
205-
animations: { () -> Void in
206-
waveformView.alpha = 1.0
207-
})
208-
}
248+
private func handleRenderComplete() {
249+
guard let startTime = profilingStartTime else { return }
209250

210-
func waveformViewWillLoad(_ waveformView: FDWaveformView) {
211-
startLoading = Date()
212-
}
251+
if aacRenderTime == nil {
252+
// First render complete (AAC)
253+
aacRenderTime = Date().timeIntervalSince(startTime)
213254

214-
func waveformViewDidLoad(_ waveformView: FDWaveformView) {
215-
endLoading = Date()
216-
NSLog(
217-
"FDWaveformView loading done, took %0.3f seconds", endLoading.timeIntervalSince(startLoading))
218-
profileResult.append(String(format: " load %0.3f ", endLoading.timeIntervalSince(startLoading)))
255+
// Load second file
256+
audioURL = nil
257+
profilingStartTime = Date()
258+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
259+
if let url = Bundle.main.url(forResource: "Submarine", withExtension: "aiff") {
260+
audioURL = url
261+
}
262+
}
263+
} else {
264+
// Second render complete (MP3)
265+
let mp3RenderTime = Date().timeIntervalSince(startTime)
266+
profilingMessage = String(
267+
format: "AAC: %.3f seconds\nMP3: %.3f seconds",
268+
aacRenderTime ?? 0,
269+
mp3RenderTime
270+
)
271+
isShowingProfilingAlert = true
272+
profilingStartTime = nil
273+
}
219274
}
220275
}
276+
277+
#Preview {
278+
ContentView()
279+
}

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import PackageDescription
66
let package = Package(
77
name: "FDWaveformView",
88
platforms: [
9-
.iOS(.v12),
9+
.iOS(.v13),
1010
.macOS(.v10_14),
1111
],
1212
products: [

0 commit comments

Comments
 (0)