Last active
May 14, 2018 16:57
-
-
Save ngs/97107f068a1aaade737e026d9d72b23b 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 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