I recently needed to implement the music visualizer in iOS swift as shown in the image/gif.
I used this TempiFFT to precesses the audio input and modified the UI for the required visual effects. the main features of the visual effects are: 1) the gradient of the bars 2) the dashed effect in the bars 3) fading out the previous bars
for the fading effect i used this How To Make A Simple Drawing App with UIKit and Swift where it initially draws on temporary image and then copies to the main image with the reduced opacity (if required).
I somehow successfully implemented the features. The bars work properly but “didReceiveMemoryWarning” is called after sometime (around 3 minutes) and the app stops few seconds after that.
So how do I resolve the memory warning issue and the app stops issue
Following is the code for drawing bars:
func drawBars() {
// start drawng on temp image
UIGraphicsBeginImageContext(self.tempImage.frame.size)
// get context
let context = UIGraphicsGetCurrentContext()
tempImage.image?.draw(in: CGRect(x:0, y:0, width: tempImage.frame.width, height:tempImage.frame.height))
let height = Double(self.tempImage.frame.size.height)
let width = Double(self.tempImage.frame.size.width)
let barWidth = width / bars
//let barSpace = width / bars * 0.2
// number of bars
let count = self.fft?.numberOfBands ?? 0
// number of magnitudes
let fft = self.fft?.bandMagnitudes ?? [0, 0, 0]
if count == 0 {
return
}
// Draw the spectrum.
let maxDB: Float = 64.0
let minDB: Float = -32.0
let headroom = maxDB - minDB
//let colWidth = tempi_round_device_scale(d: CGFloat(width) / CGFloat(count))
// need to display only few (10) bars so will consider very (count/10)th bar
let divider = count / Int(bars) - 1
var barCount = 0
for i in 0..<count {
// checking if display bar or not
if i % divider != 0 || i / divider > 9 {
continue
}
let magnitude = fft[i]
// Incoming magnitudes are linear, making it impossible to see very low or very high values. Decibels to the rescue!
var magnitudeDB = TempiFFT.toDB(magnitude)
// Normalize the incoming magnitude so that -Inf = 0
magnitudeDB = max(0, magnitudeDB + abs(minDB))
let dbRatio = min(1.0, magnitudeDB / headroom)
let magnitudeNorm = CGFloat(dbRatio) * CGFloat(height)
//let colRect: CGRect = CGRect(x: x, y: plotYStart, width: colWidth, height: magnitudeNorm)
//let startPoint = CGPoint(x: viewWidth / 2, y: 0)
//let endPoint = CGPoint(x: viewWidth / 2, y: viewHeight)
//context.saveGState()
//context.clip(to: colRect)
//context.drawLinearGradient(gradient!, start: startPoint, end: endPoint, options: CGGradientDrawingOptions(rawValue: 0))
//context.restoreGState()
//x += colWidth
//}
//for var i in (0..<10) {
//print(barCount)
let x = Double(barCount) * barWidth + 0.5 * barWidth
// cliping the bar area for dash and gradient effect
context?.saveGState()
context?.addRect(CGRect(x:x - width / bars * 0.4, y:height - Double(magnitudeNorm), width:width / bars * 0.8, height:Double(magnitudeNorm)))
context?.clip()
// context?.drawLinearGradient(CGGradient(colorsSpace: CGColorSpaceCreateDeviceRGB(), colors: [UIColor.red.cgColor, UIColor.blue.cgColor] as CFArray, locations: [0, 1])! , start: CGPoint(x:x, y:height + 5), end: CGPoint(x:x, y:height - Double(magnitudeNorm)), options: CGGradientDrawingOptions(rawValue: 0))
// draw the gradient first
context?.drawLinearGradient(CGGradient(colorsSpace: CGColorSpaceCreateDeviceRGB(), colors: [UIColor.white.cgColor, UIColor.cyan.cgColor, UIColor.magenta.cgColor, UIColor.magenta.cgColor] as CFArray, locations: [0, 0.5, 0.85, 1])! , start: CGPoint(x:x, y:height + 5), end: CGPoint(x:x, y:0), options: CGGradientDrawingOptions(rawValue: 0))
context?.restoreGState()
//context?.addRect(CGRect(x:x - width / bars * 0.4, y:height - Double(magnitudeNorm), width:width / bars * 0.8, height:Double(magnitudeNorm)))
//context?.drawPath(using: CGPathDrawingMode.stroke)
// now draw the dash line
context?.move(to: CGPoint(x:x, y:height + 5))
context?.addLine(to: CGPoint(x:x, y:height - Double(magnitudeNorm)))
//context?.addLine(to: CGPoint(x:x, y:height - Double((i / divider + 1) * 10)))
context?.setLineCap(CGLineCap.butt)
context?.setLineWidth(CGFloat(width / bars * 0.85))
context?.setStrokeColor(red: 0, green: 0, blue: 0, alpha: 1)
context?.setBlendMode(CGBlendMode.normal)
context?.setLineDash(phase: 1, lengths: [5, 10])
context?.strokePath()
barCount += 1;
}
// save the image
tempImage.image = UIGraphicsGetImageFromCurrentImageContext()
tempImage.alpha = 1
UIGraphicsEndImageContext()
tempi_dispatch_main { () -> () in
//self.spectralView.fft = fft
//self.spectralView.setNeedsDisplay()
//merge the image with the previous image
//on main thread
self.refresh()
}
//refresh()
}
func refresh() {
UIGraphicsBeginImageContext(mainImage.frame.size)
//let context = UIGraphicsGetCurrentContext()
// draw the previous image with 0.9 opacity(transparancy) for a fade out effect
mainImage.image?.draw(in: CGRect(x:0, y:0, width:mainImage.frame.width, height:mainImage.frame.height), blendMode: CGBlendMode.normal, alpha: 0.9)
// draw the new bars over the previous one
tempImage.image?.draw(in: CGRect(x:0, y:0, width:mainImage.frame.width, height:mainImage.frame.height), blendMode: CGBlendMode.normal, alpha: 1)
// finally show that new image on the image view
mainImage.image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
tempImage.image = nil
}