0

I recently needed to implement the music visualizer in iOS swift as shown in the image/gif.

enter image description here

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
}
Paul Roub
  • 36,322
  • 27
  • 84
  • 93
Rakshit Tanti
  • 147
  • 2
  • 5

0 Answers0