Skip to content

Instantly share code, notes, and snippets.

@trilliwon
Created August 2, 2020 06:51
Show Gist options
  • Save trilliwon/f2b2d5a001c5391b1fd9f9b0a5889f97 to your computer and use it in GitHub Desktop.
Save trilliwon/f2b2d5a001c5391b1fd9f9b0a5889f97 to your computer and use it in GitHub Desktop.
import UIKit
private let kOscillationAnimationKey = "oscillation"
class MusicEqualizerView: UIView {
var barCount: Int = 4
var barInterval: CGFloat = 0.2
var barIdleHeight: CGFloat = 0.2
var barMinPeakHeight: CGFloat = 0.3
var barMaxPeakHeight: CGFloat = 1.0
var barOpacity: Float = 0.7
var roundEdge: Bool = true
private var barLayers = [CALayer]()
override init(frame: CGRect) {
super.init(frame: frame)
prepare()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
prepare()
}
func start() {
stop()
let basePeriod: CFTimeInterval = Double.random(in: (0.8...1.0))
for layer in barLayers {
startOscillating(layer: layer, basePeriod: basePeriod)
}
}
func stop() {
for layer in barLayers {
layer.removeAnimation(forKey: kOscillationAnimationKey)
}
}
var isAnimating: Bool {
get { return barLayers.first?.animation(forKey: kOscillationAnimationKey) != nil }
}
override func tintColorDidChange() {
super.tintColorDidChange()
for layer in barLayers {
layer.backgroundColor = UIColor.white.cgColor
}
}
func prepare() {
barLayers.removeAll()
for i in 0..<barCount {
let layer = createBarLayer(index: i)
barLayers.append(layer)
self.layer.addSublayer(layer)
}
}
private func createBarLayer(index: Int) -> CALayer {
let intervalWidth = bounds.width * barInterval
let barWidth = (bounds.width - intervalWidth * CGFloat(barCount - 1)) / CGFloat(barCount)
let xOffset = barWidth * CGFloat(index) + intervalWidth * CGFloat(index)
let maxPeakHeight = bounds.height * barMaxPeakHeight
let idleHeight = bounds.height * barIdleHeight
let layer = CALayer()
layer.backgroundColor = UIColor.white.cgColor
layer.opacity = barOpacity
layer.anchorPoint = CGPoint(x: 0.0, y: 1)
layer.position = CGPoint(x: xOffset, y: maxPeakHeight)
layer.bounds = CGRect(x: 0.0, y: 0.0, width: barWidth, height: idleHeight) // In its own coordinate
if roundEdge {
let path = UIBezierPath(
roundedRect: CGRect(x: 0.0, y: 0.0, width: barWidth, height: maxPeakHeight),
byRoundingCorners: [.topLeft, .topRight],
cornerRadii: CGSize(width: barWidth / 2, height: barWidth / 2)
)
let mask = CAShapeLayer()
mask.path = path.cgPath
layer.mask = mask
}
return layer
}
private func startOscillating(layer: CALayer, basePeriod: CFTimeInterval) {
let maxPeakHeight = bounds.height * barMaxPeakHeight
let minPeakHeight = bounds.height * barMinPeakHeight
let peakHeight: CGFloat = CGFloat.random(in: (minPeakHeight...maxPeakHeight))
var toBounds = layer.bounds
toBounds.size.height = peakHeight
let animation = CABasicAnimation(keyPath: "bounds")
animation.fromValue = layer.bounds
animation.toValue = toBounds
animation.repeatCount = Float.greatestFiniteMagnitude
animation.autoreverses = true
animation.duration = (basePeriod / 2) * Double((maxPeakHeight / peakHeight));
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn)
animation.isRemovedOnCompletion = false
layer.add(animation, forKey: kOscillationAnimationKey)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment