Created
August 2, 2020 06:51
-
-
Save trilliwon/f2b2d5a001c5391b1fd9f9b0a5889f97 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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