Skip to content

Instantly share code, notes, and snippets.

@ngs
Last active May 14, 2018 16:57
Show Gist options
  • Select an option

  • Save ngs/97107f068a1aaade737e026d9d72b23b to your computer and use it in GitHub Desktop.

Select an option

Save ngs/97107f068a1aaade737e026d9d72b23b to your computer and use it in GitHub Desktop.
import Foundation
import PlaygroundSupport
import UIKit
import QuartzCore
class IndicatorView: UIView {
private enum GroupAnimationKey: String {
case inner = "inner-circle-group-animation"
case outer = "outer-circle-group-animation"
}
@IBInspectable var circleColor: UIColor? = nil {
didSet {
circle.fillColor = circleColor?.cgColor
}
}
@IBInspectable var innerCircleColor: UIColor? = nil {
didSet {
innerCircle.fillColor = innerCircleColor?.cgColor
}
}
@IBInspectable var outerCircleColor: UIColor? = nil {
didSet {
outerCircle.fillColor = outerCircleColor?.cgColor
}
}
lazy var circle: CAShapeLayer = {
return createCircleLayer(at: 2)
}()
lazy var innerCircle: CAShapeLayer = {
return createCircleLayer(at: 1)
}()
lazy var outerCircle: CAShapeLayer = {
return createCircleLayer(at: 0)
}()
@objc func handleSliderChange(_ sender: Any) {
guard let slider = sender as? UISlider else { return }
let value = slider.value
showEffect(withVolume: value)
}
func showEffect(withVolume volume: Float) {
animate(withKey: .inner, volume: volume)
animate(withKey: .outer, volume: volume)
}
private func createCircleLayer(at idx: UInt32) -> CAShapeLayer {
let shapeLayer = CAShapeLayer()
layer.insertSublayer(shapeLayer, at: idx)
return shapeLayer
}
private func animate(withKey key: GroupAnimationKey, volume: Float) {
let duration: CFTimeInterval
let shapeLayer: CAShapeLayer
switch key {
case .inner:
duration = 0.2
shapeLayer = innerCircle
case .outer:
duration = 0.5
shapeLayer = outerCircle
}
let growDuration: CFTimeInterval = 0.2
let totalDuration = duration + growDuration
let keyTimes: [NSNumber] = [
NSNumber(value: growDuration),
NSNumber(value: duration)
]
let timingFunctions = [
CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn),
CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
]
let limitedVolume = volume > 1.0 ? 1.0 : volume
let minOpacity: Float = 0.4
let targetOpacity: Float = minOpacity + (1.0 - limitedVolume) * (1.0 - minOpacity)
let opacities: [NSNumber] = [NSNumber(value: targetOpacity), 1.0]
let maxScale: CGFloat = 0.2
let targetScale: CGFloat = 1.0 + maxScale * CGFloat(volume)
let sizes: [NSValue] = [
NSValue(cgRect: CGRect(x: 0, y: 0, width: bounds.width * targetScale, height: bounds.height * targetScale)),
NSValue(cgRect: bounds)]
let opacityKeyFrame = CAKeyframeAnimation(keyPath: "opacity")
opacityKeyFrame.values = opacities
opacityKeyFrame.keyTimes = keyTimes
opacityKeyFrame.duration = totalDuration
opacityKeyFrame.timingFunctions = timingFunctions
let scaleKeyFrame = CAKeyframeAnimation(keyPath: "bounds")
scaleKeyFrame.values = sizes
scaleKeyFrame.keyTimes = keyTimes
scaleKeyFrame.duration = totalDuration
scaleKeyFrame.timingFunctions = timingFunctions
let animationGroup = CAAnimationGroup()
animationGroup.setValue(key.rawValue, forKey: "animationName")
animationGroup.animations = [opacityKeyFrame, scaleKeyFrame]
animationGroup.duration = totalDuration
shapeLayer.removeAnimation(forKey: key.rawValue)
shapeLayer.add(animationGroup, forKey: key.rawValue)
}
override func layoutSubviews() {
super.layoutSubviews()
let path = UIBezierPath(ovalIn: CGRect(
x: bounds.size.width / 2,
y: bounds.size.height / 2,
width: bounds.size.width,
height: bounds.size.height
))
let anchorPoint = CGPoint(x: 0.5, y: 0.5)
circle.path = path.cgPath
innerCircle.path = path.cgPath
outerCircle.path = path.cgPath
circle.anchorPoint = anchorPoint
innerCircle.anchorPoint = anchorPoint
outerCircle.anchorPoint = anchorPoint
circle.bounds = bounds
innerCircle.bounds = bounds
outerCircle.bounds = bounds
}
}
let iv = IndicatorView(frame: CGRect(x: 100, y: 100, width: 120, height: 120))
iv.backgroundColor = #colorLiteral(red: 0.6000000238, green: 0.6000000238, blue: 0.6000000238, alpha: 1)
iv.circleColor = #colorLiteral(red: 0.920277897, green: 0.3422145936, blue: 0.3680711259, alpha: 1)
iv.innerCircleColor = #colorLiteral(red: 0.920277897, green: 0.3422145936, blue: 0.3680711259, alpha: 1)
iv.outerCircleColor = #colorLiteral(red: 0.9215686275, green: 0.5227952878, blue: 0.541656189, alpha: 1)
let wrapper = UIView(frame: CGRect(x: 0, y: 0, width: 400, height: 400))
let slider = UISlider(frame: CGRect(x: 50, y: 330, width: 200, height: 20))
let imageView = UIImageView(image: UIImage(named: "SpeakButton"))
slider.addTarget(iv, action: #selector(IndicatorView.handleSliderChange(_:)), for: .valueChanged)
wrapper.addSubview(iv)
wrapper.addSubview(slider)
wrapper.addSubview(imageView)
imageView.center = iv.center
PlaygroundPage.current.liveView = wrapper
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment