ChartEasingOption // 平滑过度选项 @objc public enum ChartEasingOption: Int { case Linear case EaseInQuad case EaseOutQuad case EaseInOutQuad case EaseInCubic case EaseOutCubic case EaseInOutCubic case EaseInQuart case EaseOutQuart case EaseInOutQuart case EaseInQuint case EaseOutQuint case EaseInOutQuint case EaseInSine case EaseOutSine case EaseInOutSine case EaseInExpo case EaseOutExpo case EaseInOutExpo case EaseInCirc case EaseOutCirc case EaseInOutCirc case EaseInElastic case EaseOutElastic case EaseInOutElastic case EaseInBack case EaseOutBack case EaseInOutBack case EaseInBounce case EaseOutBounce case EaseInOutBounce } // elapsed: v. 时间过去;消逝(elapse的过去分词) // duration: n. 持续,持续的时间,期间 public typealias ChartEasingFunctionBlock = ((elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat) // 根据动画过渡类型,获取动画函数 internal func easingFunctionFromOption(easing: ChartEasingOption) -> ChartEasingFunctionBlock { switch easing { case .Linear: return EasingFunctions.Linear case .EaseInQuad: return EasingFunctions.EaseInQuad case .EaseOutQuad: return EasingFunctions.EaseOutQuad case .EaseInOutQuad: return EasingFunctions.EaseInOutQuad case .EaseInCubic: return EasingFunctions.EaseInCubic case .EaseOutCubic: return EasingFunctions.EaseOutCubic case .EaseInOutCubic: return EasingFunctions.EaseInOutCubic case .EaseInQuart: return EasingFunctions.EaseInQuart case .EaseOutQuart: return EasingFunctions.EaseOutQuart case .EaseInOutQuart: return EasingFunctions.EaseInOutQuart case .EaseInQuint: return EasingFunctions.EaseInQuint case .EaseOutQuint: return EasingFunctions.EaseOutQuint case .EaseInOutQuint: return EasingFunctions.EaseInOutQuint case .EaseInSine: return EasingFunctions.EaseInSine case .EaseOutSine: return EasingFunctions.EaseOutSine case .EaseInOutSine: return EasingFunctions.EaseInOutSine case .EaseInExpo: return EasingFunctions.EaseInExpo case .EaseOutExpo: return EasingFunctions.EaseOutExpo case .EaseInOutExpo: return EasingFunctions.EaseInOutExpo case .EaseInCirc: return EasingFunctions.EaseInCirc case .EaseOutCirc: return EasingFunctions.EaseOutCirc case .EaseInOutCirc: return EasingFunctions.EaseInOutCirc case .EaseInElastic: return EasingFunctions.EaseInElastic case .EaseOutElastic: return EasingFunctions.EaseOutElastic case .EaseInOutElastic: return EasingFunctions.EaseInOutElastic case .EaseInBack: return EasingFunctions.EaseInBack case .EaseOutBack: return EasingFunctions.EaseOutBack case .EaseInOutBack: return EasingFunctions.EaseInOutBack case .EaseInBounce: return EasingFunctions.EaseInBounce case .EaseOutBounce: return EasingFunctions.EaseOutBounce case .EaseInOutBounce: return EasingFunctions.EaseInOutBounce } } // 平滑过度函数 internal struct EasingFunctions { internal static let Linear = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in return CGFloat(elapsed / duration); } internal static let EaseInQuad = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in var position = CGFloat(elapsed / duration) return position * position } internal static let EaseOutQuad = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in var position = CGFloat(elapsed / duration) return -position * (position - 2.0) } internal static let EaseInOutQuad = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in var position = CGFloat(elapsed / (duration / 2.0)) if (position < 1.0) { return 0.5 * position * position } return -0.5 * ((--position) * (position - 2.0) - 1.0) } internal static let EaseInCubic = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in var position = CGFloat(elapsed / duration) return position * position * position } internal static let EaseOutCubic = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in var position = CGFloat(elapsed / duration) position-- return (position * position * position + 1.0) } internal static let EaseInOutCubic = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in var position = CGFloat(elapsed / (duration / 2.0)) if (position < 1.0) { return 0.5 * position * position * position } position -= 2.0 return 0.5 * (position * position * position + 2.0) } internal static let EaseInQuart = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in var position = CGFloat(elapsed / duration) return position * position * position * position } internal static let EaseOutQuart = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in var position = CGFloat(elapsed / duration) position-- return -(position * position * position * position - 1.0) } internal static let EaseInOutQuart = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in var position = CGFloat(elapsed / (duration / 2.0)) if (position < 1.0) { return 0.5 * position * position * position * position } position -= 2.0 return -0.5 * (position * position * position * position - 2.0) } internal static let EaseInQuint = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in var position = CGFloat(elapsed / duration) return position * position * position * position * position } internal static let EaseOutQuint = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in var position = CGFloat(elapsed / duration) position-- return (position * position * position * position * position + 1.0) } internal static let EaseInOutQuint = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in var position = CGFloat(elapsed / (duration / 2.0)) if (position < 1.0) { return 0.5 * position * position * position * position * position } else { position -= 2.0 return 0.5 * (position * position * position * position * position + 2.0) } } internal static let EaseInSine = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in var position: NSTimeInterval = elapsed / duration return CGFloat( -cos(position * M_PI_2) + 1.0 ) } internal static let EaseOutSine = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in var position: NSTimeInterval = elapsed / duration return CGFloat( sin(position * M_PI_2) ) } internal static let EaseInOutSine = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in var position: NSTimeInterval = elapsed / duration return CGFloat( -0.5 * (cos(M_PI * position) - 1.0) ) } internal static let EaseInExpo = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in return (elapsed == 0) ? 0.0 : CGFloat(pow(2.0, 10.0 * (elapsed / duration - 1.0))) } internal static let EaseOutExpo = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in return (elapsed == duration) ? 1.0 : (-CGFloat(pow(2.0, -10.0 * elapsed / duration)) + 1.0) } internal static let EaseInOutExpo = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in if (elapsed == 0) { return 0.0 } if (elapsed == duration) { return 1.0 } var position: NSTimeInterval = elapsed / (duration / 2.0) if (position < 1.0) { return CGFloat( 0.5 * pow(2.0, 10.0 * (position - 1.0)) ) } return CGFloat( 0.5 * (-pow(2.0, -10.0 * --position) + 2.0) ) } internal static let EaseInCirc = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in var position = CGFloat(elapsed / duration) return -(CGFloat(sqrt(1.0 - position * position)) - 1.0) } internal static let EaseOutCirc = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in var position = CGFloat(elapsed / duration) position-- return CGFloat( sqrt(1 - position * position) ) } internal static let EaseInOutCirc = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in var position: NSTimeInterval = elapsed / (duration / 2.0) if (position < 1.0) { return CGFloat( -0.5 * (sqrt(1.0 - position * position) - 1.0) ) } position -= 2.0 return CGFloat( 0.5 * (sqrt(1.0 - position * position) + 1.0) ) } internal static let EaseInElastic = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in if (elapsed == 0.0) { return 0.0 } var position: NSTimeInterval = elapsed / duration if (position == 1.0) { return 1.0 } var p = duration * 0.3 var s = p / (2.0 * M_PI) * asin(1.0) position -= 1.0 return CGFloat( -(pow(2.0, 10.0 * position) * sin((position * duration - s) * (2.0 * M_PI) / p)) ) } internal static let EaseOutElastic = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in if (elapsed == 0.0) { return 0.0 } var position: NSTimeInterval = elapsed / duration if (position == 1.0) { return 1.0 } var p = duration * 0.3 var s = p / (2.0 * M_PI) * asin(1.0) return CGFloat( pow(2.0, -10.0 * position) * sin((position * duration - s) * (2.0 * M_PI) / p) + 1.0 ) } internal static let EaseInOutElastic = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in if (elapsed == 0.0) { return 0.0 } var position: NSTimeInterval = elapsed / (duration / 2.0) if (position == 2.0) { return 1.0 } var p = duration * (0.3 * 1.5) var s = p / (2.0 * M_PI) * asin(1.0) if (position < 1.0) { position -= 1.0 return CGFloat( -0.5 * (pow(2.0, 10.0 * position) * sin((position * duration - s) * (2.0 * M_PI) / p)) ) } position -= 1.0 return CGFloat( pow(2.0, -10.0 * position) * sin((position * duration - s) * (2.0 * M_PI) / p) * 0.5 + 1.0 ) } internal static let EaseInBack = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in let s: NSTimeInterval = 1.70158 var position: NSTimeInterval = elapsed / duration return CGFloat( position * position * ((s + 1.0) * position - s) ) } internal static let EaseOutBack = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in let s: NSTimeInterval = 1.70158 var position: NSTimeInterval = elapsed / duration position-- return CGFloat( (position * position * ((s + 1.0) * position + s) + 1.0) ) } internal static let EaseInOutBack = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in var s: NSTimeInterval = 1.70158 var position: NSTimeInterval = elapsed / (duration / 2.0) if (position < 1.0) { s *= 1.525 return CGFloat( 0.5 * (position * position * ((s + 1.0) * position - s)) ) } s *= 1.525 position -= 2.0 return CGFloat( 0.5 * (position * position * ((s + 1.0) * position + s) + 2.0) ) } internal static let EaseInBounce = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in return 1.0 - EaseOutBounce(duration - elapsed, duration) } internal static let EaseOutBounce = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in var position: NSTimeInterval = elapsed / duration if (position < (1.0 / 2.75)) { return CGFloat( 7.5625 * position * position ) } else if (position < (2.0 / 2.75)) { position -= (1.5 / 2.75) return CGFloat( 7.5625 * position * position + 0.75 ) } else if (position < (2.5 / 2.75)) { position -= (2.25 / 2.75) return CGFloat( 7.5625 * position * position + 0.9375 ) } else { position -= (2.625 / 2.75) return CGFloat( 7.5625 * position * position + 0.984375 ) } } internal static let EaseInOutBounce = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in if (elapsed < (duration / 2.0)) { return EaseInBounce(elapsed * 2.0, duration) * 0.5 } return EaseOutBounce(elapsed * 2.0 - duration, duration) * 0.5 + 0.5 } } ChartAnimator @objc public protocol ChartAnimatorDelegate { /// Called when the Animator has stepped. /// 动画执行时调用 func chartAnimatorUpdated(chartAnimator: ChartAnimator) /// Called when the Animator has stopped. /// 动画结束时调用 func chartAnimatorStopped(chartAnimator: ChartAnimator) } public class ChartAnimator: NSObject { public weak var delegate: ChartAnimatorDelegate? public var updateBlock: (() -> Void)? public var stopBlock: (() -> Void)? /// the phase that is animated and influences the drawn values on the y-axis /// x相位 影响y轴绘值 public var phaseX: CGFloat = 1.0 /// the phase that is animated and influences the drawn values on the y-axis /// y相位 影响y轴绘值 public var phaseY: CGFloat = 1.0 private var _startTime: NSTimeInterval = 0.0 private var _displayLink: CADisplayLink! private var _xDuration: NSTimeInterval = 0.0 private var _yDuration: NSTimeInterval = 0.0 private var _endTimeX: NSTimeInterval = 0.0 private var _endTimeY: NSTimeInterval = 0.0 private var _endTime: NSTimeInterval = 0.0 private var _enabledX: Bool = false private var _enabledY: Bool = false private var _easingX: ChartEasingFunctionBlock? private var _easingY: ChartEasingFunctionBlock? public override init() { super.init() } deinit { stop() } public func stop() { if (_displayLink != nil) { _displayLink.removeFromRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes) _displayLink = nil _enabledX = false _enabledY = false if (delegate != nil) { delegate!.chartAnimatorStopped(self) } if (stopBlock != nil) { stopBlock?() } } } private func updateAnimationPhases(currentTime: NSTimeInterval) { var elapsedTime: NSTimeInterval = currentTime - _startTime if (_enabledX) { var duration: NSTimeInterval = _xDuration var elapsed: NSTimeInterval = elapsedTime if (elapsed > duration) { elapsed = duration } if (_easingX != nil) { phaseX = _easingX!(elapsed: elapsed, duration: duration) } else { phaseX = CGFloat(elapsed / duration) } } if (_enabledY) { var duration: NSTimeInterval = _yDuration var elapsed: NSTimeInterval = elapsedTime if (elapsed > duration) { elapsed = duration } if (_easingY != nil) { phaseY = _easingY!(elapsed: elapsed, duration: duration) } else { phaseY = CGFloat(elapsed / duration) } } } @objc private func animationLoop() { var currentTime: NSTimeInterval = CACurrentMediaTime() updateAnimationPhases(currentTime) if (delegate != nil) { delegate!.chartAnimatorUpdated(self) } if (updateBlock != nil) { updateBlock!() } if (currentTime >= _endTime) { stop() } } /// Animates the drawing / rendering of the chart on both x- and y-axis with the specified animation time. /// If animate(...) is called, no further calling of invalidate() is necessary to refresh the chart. /// :param: xAxisDuration duration for animating the x axis /// :param: yAxisDuration duration for animating the y axis /// :param: easingX an easing function for the animation on the x axis /// :param: easingY an easing function for the animation on the y axis public func animate(#xAxisDuration: NSTimeInterval, yAxisDuration: NSTimeInterval, easingX: ChartEasingFunctionBlock?, easingY: ChartEasingFunctionBlock?) { stop() _displayLink = CADisplayLink(target: self, selector: Selector("animationLoop")) _startTime = CACurrentMediaTime() _xDuration = xAxisDuration _yDuration = yAxisDuration _endTimeX = _startTime + xAxisDuration _endTimeY = _startTime + yAxisDuration _endTime = _endTimeX > _endTimeY ? _endTimeX : _endTimeY _enabledX = xAxisDuration > 0.0 _enabledY = yAxisDuration > 0.0 _easingX = easingX _easingY = easingY // Take care of the first frame if rendering is already scheduled... updateAnimationPhases(_startTime) if (_enabledX || _enabledY) { _displayLink.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes) } } /// Animates the drawing / rendering of the chart on both x- and y-axis with the specified animation time. /// If animate(...) is called, no further calling of invalidate() is necessary to refresh the chart. /// :param: xAxisDuration duration for animating the x axis /// :param: yAxisDuration duration for animating the y axis /// :param: easingOptionX the easing function for the animation on the x axis /// :param: easingOptionY the easing function for the animation on the y axis public func animate(#xAxisDuration: NSTimeInterval, yAxisDuration: NSTimeInterval, easingOptionX: ChartEasingOption, easingOptionY: ChartEasingOption) { animate(xAxisDuration: xAxisDuration, yAxisDuration: yAxisDuration, easingX: easingFunctionFromOption(easingOptionX), easingY: easingFunctionFromOption(easingOptionY)) } /// Animates the drawing / rendering of the chart on both x- and y-axis with the specified animation time. /// If animate(...) is called, no further calling of invalidate() is necessary to refresh the chart. /// :param: xAxisDuration duration for animating the x axis /// :param: yAxisDuration duration for animating the y axis /// :param: easing an easing function for the animation public func animate(#xAxisDuration: NSTimeInterval, yAxisDuration: NSTimeInterval, easing: ChartEasingFunctionBlock?) { animate(xAxisDuration: xAxisDuration, yAxisDuration: yAxisDuration, easingX: easing, easingY: easing) } /// Animates the drawing / rendering of the chart on both x- and y-axis with the specified animation time. /// If animate(...) is called, no further calling of invalidate() is necessary to refresh the chart. /// :param: xAxisDuration duration for animating the x axis /// :param: yAxisDuration duration for animating the y axis /// :param: easingOption the easing function for the animation public func animate(#xAxisDuration: NSTimeInterval, yAxisDuration: NSTimeInterval, easingOption: ChartEasingOption) { animate(xAxisDuration: xAxisDuration, yAxisDuration: yAxisDuration, easing: easingFunctionFromOption(easingOption)) } /// Animates the drawing / rendering of the chart on both x- and y-axis with the specified animation time. /// If animate(...) is called, no further calling of invalidate() is necessary to refresh the chart. /// :param: xAxisDuration duration for animating the x axis /// :param: yAxisDuration duration for animating the y axis public func animate(#xAxisDuration: NSTimeInterval, yAxisDuration: NSTimeInterval) { animate(xAxisDuration: xAxisDuration, yAxisDuration: yAxisDuration, easingOption: .EaseInOutSine) } /// Animates the drawing / rendering of the chart the x-axis with the specified animation time. /// If animate(...) is called, no further calling of invalidate() is necessary to refresh the chart. /// :param: xAxisDuration duration for animating the x axis /// :param: easing an easing function for the animation public func animate(#xAxisDuration: NSTimeInterval, easing: ChartEasingFunctionBlock?) { animate(xAxisDuration: xAxisDuration, yAxisDuration: 0.0, easing: easing) } /// Animates the drawing / rendering of the chart the x-axis with the specified animation time. /// If animate(...) is called, no further calling of invalidate() is necessary to refresh the chart. /// :param: xAxisDuration duration for animating the x axis /// :param: easingOption the easing function for the animation public func animate(#xAxisDuration: NSTimeInterval, easingOption: ChartEasingOption) { animate(xAxisDuration: xAxisDuration, yAxisDuration: 0.0, easing: easingFunctionFromOption(easingOption)) } /// Animates the drawing / rendering of the chart the x-axis with the specified animation time. /// If animate(...) is called, no further calling of invalidate() is necessary to refresh the chart. /// :param: xAxisDuration duration for animating the x axis public func animate(#xAxisDuration: NSTimeInterval) { animate(xAxisDuration: xAxisDuration, yAxisDuration: 0.0, easingOption: .EaseInOutSine) } /// Animates the drawing / rendering of the chart the y-axis with the specified animation time. /// If animate(...) is called, no further calling of invalidate() is necessary to refresh the chart. /// :param: yAxisDuration duration for animating the y axis /// :param: easing an easing function for the animation public func animate(#yAxisDuration: NSTimeInterval, easing: ChartEasingFunctionBlock?) { animate(xAxisDuration: 0.0, yAxisDuration: yAxisDuration, easing: easing) } /// Animates the drawing / rendering of the chart the y-axis with the specified animation time. /// If animate(...) is called, no further calling of invalidate() is necessary to refresh the chart. /// :param: yAxisDuration duration for animating the y axis /// :param: easingOption the easing function for the animation public func animate(#yAxisDuration: NSTimeInterval, easingOption: ChartEasingOption) { animate(xAxisDuration: 0.0, yAxisDuration: yAxisDuration, easing: easingFunctionFromOption(easingOption)) } /// Animates the drawing / rendering of the chart the y-axis with the specified animation time. /// If animate(...) is called, no further calling of invalidate() is necessary to refresh the chart. /// :param: yAxisDuration duration for animating the y axis public func animate(#yAxisDuration: NSTimeInterval) { animate(xAxisDuration: 0.0, yAxisDuration: yAxisDuration, easingOption: .EaseInOutSine) } }