|
@@ -1,608 +0,0 @@
|
|
-//
|
|
|
|
-// UICircularRing.swift
|
|
|
|
-// UICircularProgressRing
|
|
|
|
-//
|
|
|
|
-// Copyright (c) 2019 Luis Padron
|
|
|
|
-//
|
|
|
|
-// Permission is hereby granted, free of charge, to any person obtaining a
|
|
|
|
-// copy of this software and associated documentation files (the "Software"),
|
|
|
|
-// to deal in the Software without restriction, including without limitation the
|
|
|
|
-// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
|
-// copies of the Software, and to permit persons to whom the Software is furnished
|
|
|
|
-// to do so, subject to the following conditions:
|
|
|
|
-//
|
|
|
|
-// The above copyright notice and this permission notice shall be included
|
|
|
|
-// in all copies or substantial portions of the Software.
|
|
|
|
-//
|
|
|
|
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
|
|
-// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
-// FITNESS FOR A PARTICULAR PURPOSE AND
|
|
|
|
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
|
|
|
-// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
|
|
|
|
-// OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
|
|
-// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
-//
|
|
|
|
-
|
|
|
|
-import UIKit
|
|
|
|
-
|
|
|
|
-/**
|
|
|
|
-
|
|
|
|
- # UICircularRing
|
|
|
|
-
|
|
|
|
- This is the base class of `UICircularProgressRing` and `UICircularTimerRing`.
|
|
|
|
- You should not instantiate this class, instead use one of the concrete classes provided
|
|
|
|
- or subclass and make your own.
|
|
|
|
-
|
|
|
|
- This is the UIView subclass that creates and handles everything
|
|
|
|
- to do with the circular ring.
|
|
|
|
-
|
|
|
|
- This class has a custom CAShapeLayer (`UICircularRingLayer`) which
|
|
|
|
- handels the drawing and animating of the view
|
|
|
|
-
|
|
|
|
- ## Author
|
|
|
|
- Luis Padron
|
|
|
|
-
|
|
|
|
- */
|
|
|
|
-@IBDesignable open class UICircularRing: UIView {
|
|
|
|
-
|
|
|
|
- // MARK: Circle Properties
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- Whether or not the progress ring should be a full circle.
|
|
|
|
-
|
|
|
|
- What this means is that the outer ring will always go from 0 - 360 degrees and
|
|
|
|
- the inner ring will be calculated accordingly depending on current value.
|
|
|
|
-
|
|
|
|
- ## Important ##
|
|
|
|
- Default = true
|
|
|
|
-
|
|
|
|
- When this property is true any value set for `endAngle` will be ignored.
|
|
|
|
-
|
|
|
|
- ## Author
|
|
|
|
- Luis Padron
|
|
|
|
-
|
|
|
|
- */
|
|
|
|
- @IBInspectable open var fullCircle: Bool = true {
|
|
|
|
- didSet { ringLayer.setNeedsDisplay() }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // MARK: View Style
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- The style of the progress ring.
|
|
|
|
-
|
|
|
|
- Type: `UICircularRingStyle`
|
|
|
|
-
|
|
|
|
- The five styles include `inside`, `ontop`, `dashed`, `dotted`, and `gradient`
|
|
|
|
-
|
|
|
|
- ## Important ##
|
|
|
|
- Default = UICircularRingStyle.inside
|
|
|
|
-
|
|
|
|
- ## Author
|
|
|
|
- Luis Padron
|
|
|
|
- */
|
|
|
|
- open var style: UICircularRingStyle = .inside {
|
|
|
|
- didSet { ringLayer.setNeedsDisplay() }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- The options for a gradient ring.
|
|
|
|
-
|
|
|
|
- If this is non-`nil` then a gradient style will be applied.
|
|
|
|
-
|
|
|
|
- ## Important ##
|
|
|
|
- Default = `nil`
|
|
|
|
- */
|
|
|
|
- open var gradientOptions: UICircularRingGradientOptions? = nil {
|
|
|
|
- didSet { ringLayer.setNeedsDisplay() }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- A toggle for showing or hiding the value label.
|
|
|
|
- If false the current value will not be shown.
|
|
|
|
-
|
|
|
|
- ## Important ##
|
|
|
|
- Default = true
|
|
|
|
-
|
|
|
|
- ## Author
|
|
|
|
- Luis Padron
|
|
|
|
- */
|
|
|
|
- @IBInspectable public var shouldShowValueText: Bool = true {
|
|
|
|
- didSet { ringLayer.setNeedsDisplay() }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- Style for the value knob, default is `nil`.
|
|
|
|
-
|
|
|
|
- ## Important ##
|
|
|
|
- If this is `nil`, no value knob is shown.
|
|
|
|
-
|
|
|
|
- */
|
|
|
|
- open var valueKnobStyle: UICircularRingValueKnobStyle? {
|
|
|
|
- didSet { ringLayer.setNeedsDisplay() }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- The start angle for the entire progress ring view.
|
|
|
|
-
|
|
|
|
- Please note that Cocoa Touch uses a clockwise rotating unit circle.
|
|
|
|
- I.e: 90 degrees is at the bottom and 270 degrees is at the top
|
|
|
|
-
|
|
|
|
- ## Important ##
|
|
|
|
- Default = 0 (degrees)
|
|
|
|
-
|
|
|
|
- Values should be in degrees (they're converted to radians internally)
|
|
|
|
-
|
|
|
|
- ## Author
|
|
|
|
- Luis Padron
|
|
|
|
- */
|
|
|
|
- @IBInspectable open var startAngle: CGFloat = 0 {
|
|
|
|
- didSet { ringLayer.setNeedsDisplay() }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- The end angle for the entire progress ring
|
|
|
|
-
|
|
|
|
- Please note that Cocoa Touch uses a clockwise rotating unit circle.
|
|
|
|
- I.e: 90 degrees is at the bottom and 270 degrees is at the top
|
|
|
|
-
|
|
|
|
- ## Important ##
|
|
|
|
- Default = 360 (degrees)
|
|
|
|
-
|
|
|
|
- Values should be in degrees (they're converted to radians internally)
|
|
|
|
-
|
|
|
|
- ## Author
|
|
|
|
- Luis Padron
|
|
|
|
- */
|
|
|
|
- @IBInspectable open var endAngle: CGFloat = 360 {
|
|
|
|
- didSet { ringLayer.setNeedsDisplay() }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // MARK: Outer Ring properties
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- The width of the outer ring for the progres bar
|
|
|
|
-
|
|
|
|
- ## Important ##
|
|
|
|
- Default = 10.0
|
|
|
|
-
|
|
|
|
- ## Author
|
|
|
|
- Luis Padron
|
|
|
|
- */
|
|
|
|
- @IBInspectable open var outerRingWidth: CGFloat = 10.0 {
|
|
|
|
- didSet { ringLayer.setNeedsDisplay() }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- The color for the outer ring
|
|
|
|
-
|
|
|
|
- ## Important ##
|
|
|
|
- Default = UIColor.gray
|
|
|
|
-
|
|
|
|
- ## Author
|
|
|
|
- Luis Padron
|
|
|
|
- */
|
|
|
|
- @IBInspectable open var outerRingColor: UIColor = UIColor.gray {
|
|
|
|
- didSet { ringLayer.setNeedsDisplay() }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- The style for the tip/cap of the outer ring
|
|
|
|
-
|
|
|
|
- Type: `CGLineCap`
|
|
|
|
-
|
|
|
|
- ## Important ##
|
|
|
|
- Default = CGLineCap.butt
|
|
|
|
-
|
|
|
|
- This is only noticible when ring is not a full circle.
|
|
|
|
-
|
|
|
|
- ## Author
|
|
|
|
- Luis Padron
|
|
|
|
- */
|
|
|
|
- open var outerCapStyle: CGLineCap = .butt {
|
|
|
|
- didSet { ringLayer.setNeedsDisplay() }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // MARK: Inner Ring properties
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- The width of the inner ring for the progres bar
|
|
|
|
-
|
|
|
|
- ## Important ##
|
|
|
|
- Default = 5.0
|
|
|
|
-
|
|
|
|
- ## Author
|
|
|
|
- Luis Padron
|
|
|
|
- */
|
|
|
|
- @IBInspectable open var innerRingWidth: CGFloat = 5.0 {
|
|
|
|
- didSet { ringLayer.setNeedsDisplay() }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- The color of the inner ring for the progres bar
|
|
|
|
-
|
|
|
|
- ## Important ##
|
|
|
|
- Default = UIColor.blue
|
|
|
|
-
|
|
|
|
- ## Author
|
|
|
|
- Luis Padron
|
|
|
|
- */
|
|
|
|
- @IBInspectable open var innerRingColor: UIColor = UIColor.blue {
|
|
|
|
- didSet { ringLayer.setNeedsDisplay() }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- The spacing between the outer ring and inner ring
|
|
|
|
-
|
|
|
|
- ## Important ##
|
|
|
|
- This only applies when using progressRingStyle = 1
|
|
|
|
-
|
|
|
|
- Default = 1
|
|
|
|
-
|
|
|
|
- ## Author
|
|
|
|
- Luis Padron
|
|
|
|
- */
|
|
|
|
- @IBInspectable open var innerRingSpacing: CGFloat = 1 {
|
|
|
|
- didSet { ringLayer.setNeedsDisplay() }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- The style for the tip/cap of the inner ring
|
|
|
|
-
|
|
|
|
- Type: `CGLineCap`
|
|
|
|
-
|
|
|
|
- ## Important ##
|
|
|
|
- Default = CGLineCap.round
|
|
|
|
-
|
|
|
|
- ## Author
|
|
|
|
- Luis Padron
|
|
|
|
- */
|
|
|
|
- open var innerCapStyle: CGLineCap = .round {
|
|
|
|
- didSet { ringLayer.setNeedsDisplay() }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // MARK: Label
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- The text color for the value label field
|
|
|
|
-
|
|
|
|
- ## Important ##
|
|
|
|
- Default = UIColor.black
|
|
|
|
-
|
|
|
|
-
|
|
|
|
- ## Author
|
|
|
|
- Luis Padron
|
|
|
|
- */
|
|
|
|
- @IBInspectable open var fontColor: UIColor = UIColor.black {
|
|
|
|
- didSet { ringLayer.setNeedsDisplay() }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- The font to be used for the progress indicator.
|
|
|
|
- All font attributes are specified here except for font color, which is done
|
|
|
|
- using `fontColor`.
|
|
|
|
-
|
|
|
|
-
|
|
|
|
- ## Important ##
|
|
|
|
- Default = UIFont.systemFont(ofSize: 18)
|
|
|
|
-
|
|
|
|
-
|
|
|
|
- ## Author
|
|
|
|
- Luis Padron
|
|
|
|
- */
|
|
|
|
- @IBInspectable open var font: UIFont = UIFont.systemFont(ofSize: 18) {
|
|
|
|
- didSet { ringLayer.setNeedsDisplay() }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- This returns whether or not the ring is currently animating
|
|
|
|
-
|
|
|
|
- ## Important ##
|
|
|
|
- Get only property
|
|
|
|
-
|
|
|
|
- ## Author
|
|
|
|
- Luis Padron
|
|
|
|
- */
|
|
|
|
- open var isAnimating: Bool {
|
|
|
|
- return ringLayer.animation(forKey: .value) != nil
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- The direction the circle is drawn in
|
|
|
|
- Example: true -> clockwise
|
|
|
|
-
|
|
|
|
- ## Important ##
|
|
|
|
- Default = true (draw the circle clockwise)
|
|
|
|
-
|
|
|
|
- ## Author
|
|
|
|
- Pete Walker
|
|
|
|
- */
|
|
|
|
- @IBInspectable open var isClockwise: Bool = true {
|
|
|
|
- didSet { ringLayer.setNeedsDisplay() }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- Typealias for animateProperties(duration:animations:completion:) fucntion completion
|
|
|
|
- */
|
|
|
|
- public typealias PropertyAnimationCompletion = (() -> Void)
|
|
|
|
-
|
|
|
|
- // MARK: Private / internal
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- Set the ring layer to the default layer, cated as custom layer
|
|
|
|
- */
|
|
|
|
- var ringLayer: UICircularRingLayer {
|
|
|
|
- // swiftlint:disable:next force_cast
|
|
|
|
- return layer as! UICircularRingLayer
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /// This variable stores how long remains on the timer when it's paused
|
|
|
|
- private var pausedTimeRemaining: TimeInterval = 0
|
|
|
|
-
|
|
|
|
- /// Used to determine when the animation was paused
|
|
|
|
- private var animationPauseTime: CFTimeInterval?
|
|
|
|
-
|
|
|
|
- /// This stores the animation when the timer is paused. We use this variable to continue the animation where it left off.
|
|
|
|
- /// See https://stackoverflow.com/questions/7568567/restoring-animation-where-it-left-off-when-app-resumes-from-background
|
|
|
|
- var snapshottedAnimation: CAAnimation?
|
|
|
|
-
|
|
|
|
- /// The completion timer, also indicates whether or not the view is animating
|
|
|
|
- var animationCompletionTimer: Timer?
|
|
|
|
-
|
|
|
|
- typealias AnimationCompletion = () -> Void
|
|
|
|
-
|
|
|
|
- // MARK: Methods
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- Overrides the default layer with the custom UICircularRingLayer class
|
|
|
|
- */
|
|
|
|
- override open class var layerClass: AnyClass {
|
|
|
|
- return UICircularRingLayer.self
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- Overriden public init to initialize the layer and view
|
|
|
|
- */
|
|
|
|
- override public init(frame: CGRect) {
|
|
|
|
- super.init(frame: frame)
|
|
|
|
- // Call the internal initializer
|
|
|
|
- initialize()
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- Overriden public init to initialize the layer and view
|
|
|
|
- */
|
|
|
|
- required public init?(coder aDecoder: NSCoder) {
|
|
|
|
- super.init(coder: aDecoder)
|
|
|
|
- // Call the internal initializer
|
|
|
|
- initialize()
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- This method initializes the custom CALayer to the default values
|
|
|
|
- */
|
|
|
|
- func initialize() {
|
|
|
|
- // This view will become the value delegate of the layer, which will call the updateValue method when needed
|
|
|
|
- ringLayer.ring = self
|
|
|
|
-
|
|
|
|
- // Helps with pixelation and blurriness on retina devices
|
|
|
|
- ringLayer.contentsScale = UIScreen.main.scale
|
|
|
|
- ringLayer.shouldRasterize = true
|
|
|
|
- ringLayer.rasterizationScale = UIScreen.main.scale * 2
|
|
|
|
- ringLayer.masksToBounds = false
|
|
|
|
-
|
|
|
|
- backgroundColor = UIColor.clear
|
|
|
|
- ringLayer.backgroundColor = UIColor.clear.cgColor
|
|
|
|
-
|
|
|
|
- NotificationCenter.default.addObserver(self,
|
|
|
|
- selector: #selector(restoreAnimation),
|
|
|
|
- name: UIApplication.willEnterForegroundNotification,
|
|
|
|
- object: nil)
|
|
|
|
-
|
|
|
|
- NotificationCenter.default.addObserver(self,
|
|
|
|
- selector: #selector(snapshotAnimation),
|
|
|
|
- name: UIApplication.willResignActiveNotification,
|
|
|
|
- object: nil)
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- Overriden because of custom layer drawing in UICircularRingLayer
|
|
|
|
- */
|
|
|
|
- open override func draw(_ rect: CGRect) {
|
|
|
|
- super.draw(rect)
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // MARK: Internal API
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- These methods are called from the layer class in order to notify
|
|
|
|
- this class about changes to the value and label display.
|
|
|
|
-
|
|
|
|
- In this base class they do nothing.
|
|
|
|
- */
|
|
|
|
-
|
|
|
|
- func didUpdateValue(newValue: CGFloat) { }
|
|
|
|
-
|
|
|
|
- func willDisplayLabel(label: UILabel) { }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- These functions are here to allow reuse between subclasses.
|
|
|
|
- They handle starting, pausing and resetting an animation of the ring.
|
|
|
|
- */
|
|
|
|
-
|
|
|
|
- func startAnimation(duration: TimeInterval, completion: @escaping AnimationCompletion) {
|
|
|
|
- if isAnimating {
|
|
|
|
- animationPauseTime = nil
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- ringLayer.timeOffset = 0
|
|
|
|
- ringLayer.beginTime = 0
|
|
|
|
- ringLayer.speed = 1
|
|
|
|
- ringLayer.animated = duration > 0
|
|
|
|
- ringLayer.animationDuration = duration
|
|
|
|
-
|
|
|
|
- // Check if a completion timer is still active and if so stop it
|
|
|
|
- animationCompletionTimer?.invalidate()
|
|
|
|
- animationCompletionTimer = Timer.scheduledTimer(timeInterval: duration,
|
|
|
|
- target: self,
|
|
|
|
- selector: #selector(self.animationDidComplete),
|
|
|
|
- userInfo: completion,
|
|
|
|
- repeats: false)
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- func pauseAnimation() {
|
|
|
|
- guard isAnimating else {
|
|
|
|
- #if DEBUG
|
|
|
|
- print("""
|
|
|
|
- UICircularProgressRing: Progress was paused without having been started.
|
|
|
|
- This has no effect but may indicate that you're unnecessarily calling this method.
|
|
|
|
- """)
|
|
|
|
- #endif
|
|
|
|
- return
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- snapshotAnimation()
|
|
|
|
-
|
|
|
|
- let pauseTime = ringLayer.convertTime(CACurrentMediaTime(), from: nil)
|
|
|
|
- animationPauseTime = pauseTime
|
|
|
|
-
|
|
|
|
- ringLayer.speed = 0.0
|
|
|
|
- ringLayer.timeOffset = pauseTime
|
|
|
|
-
|
|
|
|
- if let fireTime = animationCompletionTimer?.fireDate {
|
|
|
|
- pausedTimeRemaining = fireTime.timeIntervalSince(Date())
|
|
|
|
- } else {
|
|
|
|
- pausedTimeRemaining = 0
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- animationCompletionTimer?.invalidate()
|
|
|
|
- animationCompletionTimer = nil
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- func continueAnimation(completion: @escaping AnimationCompletion) {
|
|
|
|
- guard let pauseTime = animationPauseTime else {
|
|
|
|
- #if DEBUG
|
|
|
|
- print("""
|
|
|
|
- UICircularRing: Progress was continued without having been paused.
|
|
|
|
- This has no effect but may indicate that you're unnecessarily calling this method.
|
|
|
|
- """)
|
|
|
|
- #endif
|
|
|
|
- return
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- restoreAnimation()
|
|
|
|
-
|
|
|
|
- ringLayer.speed = 1.0
|
|
|
|
- ringLayer.timeOffset = 0.0
|
|
|
|
- ringLayer.beginTime = 0.0
|
|
|
|
-
|
|
|
|
- let timeSincePause = ringLayer.convertTime(CACurrentMediaTime(), from: nil) - pauseTime
|
|
|
|
-
|
|
|
|
- ringLayer.beginTime = timeSincePause
|
|
|
|
-
|
|
|
|
- animationCompletionTimer?.invalidate()
|
|
|
|
- animationCompletionTimer = Timer.scheduledTimer(timeInterval: pausedTimeRemaining,
|
|
|
|
- target: self,
|
|
|
|
- selector: #selector(animationDidComplete),
|
|
|
|
- userInfo: completion,
|
|
|
|
- repeats: false)
|
|
|
|
-
|
|
|
|
- animationPauseTime = nil
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- func resetAnimation() {
|
|
|
|
- ringLayer.animated = false
|
|
|
|
- ringLayer.removeAnimation(forKey: .value)
|
|
|
|
- snapshottedAnimation = nil
|
|
|
|
-
|
|
|
|
- // Stop the timer and thus make the completion method not get fired
|
|
|
|
- animationCompletionTimer?.invalidate()
|
|
|
|
- animationCompletionTimer = nil
|
|
|
|
- animationPauseTime = nil
|
|
|
|
-
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // MARK: API
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- This function allows animation of the animatable properties of the `UICircularRing`.
|
|
|
|
- These properties include `innerRingColor, innerRingWidth, outerRingColor, outerRingWidth, innerRingSpacing, fontColor`.
|
|
|
|
-
|
|
|
|
- Simply call this function and inside of the animation block change the animatable properties as you would in any `UView`
|
|
|
|
- animation block.
|
|
|
|
-
|
|
|
|
- The completion block is called when all animations finish.
|
|
|
|
- */
|
|
|
|
- open func animateProperties(duration: TimeInterval, animations: () -> Void) {
|
|
|
|
- animateProperties(duration: duration, animations: animations, completion: nil)
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- This function allows animation of the animatable properties of the `UICircularRing`.
|
|
|
|
- These properties include `innerRingColor, innerRingWidth, outerRingColor, outerRingWidth, innerRingSpacing, fontColor`.
|
|
|
|
-
|
|
|
|
- Simply call this function and inside of the animation block change the animatable properties as you would in any `UView`
|
|
|
|
- animation block.
|
|
|
|
-
|
|
|
|
- The completion block is called when all animations finish.
|
|
|
|
- */
|
|
|
|
- open func animateProperties(duration: TimeInterval, animations: () -> Void,
|
|
|
|
- completion: PropertyAnimationCompletion? = nil) {
|
|
|
|
- ringLayer.shouldAnimateProperties = true
|
|
|
|
- ringLayer.propertyAnimationDuration = duration
|
|
|
|
- CATransaction.begin()
|
|
|
|
- CATransaction.setCompletionBlock {
|
|
|
|
- // Reset and call completion
|
|
|
|
- self.ringLayer.shouldAnimateProperties = false
|
|
|
|
- self.ringLayer.propertyAnimationDuration = 0.0
|
|
|
|
- completion?()
|
|
|
|
- }
|
|
|
|
- // Commit and perform animations
|
|
|
|
- animations()
|
|
|
|
- CATransaction.commit()
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-// MARK: Helpers
|
|
|
|
-
|
|
|
|
-extension UICircularRing {
|
|
|
|
- /**
|
|
|
|
- This method is called when the application goes into the background or when the
|
|
|
|
- ProgressRing is paused using the pauseProgress method.
|
|
|
|
- This is necessary for the animation to properly pick up where it left off.
|
|
|
|
- Triggered by UIApplicationWillResignActive.
|
|
|
|
-
|
|
|
|
- ## Author
|
|
|
|
- Nicolai Cornelis
|
|
|
|
- */
|
|
|
|
- @objc func snapshotAnimation() {
|
|
|
|
- guard let animation = ringLayer.animation(forKey: .value) else { return }
|
|
|
|
- snapshottedAnimation = animation
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- This method is called when the application comes back into the foreground or
|
|
|
|
- when the ProgressRing is resumed using the continueProgress method.
|
|
|
|
- This is necessary for the animation to properly pick up where it left off.
|
|
|
|
- Triggered by UIApplicationWillEnterForeground.
|
|
|
|
-
|
|
|
|
- ## Author
|
|
|
|
- Nicolai Cornelis
|
|
|
|
- */
|
|
|
|
- @objc func restoreAnimation() {
|
|
|
|
- guard let animation = snapshottedAnimation else { return }
|
|
|
|
- ringLayer.add(animation, forKey: AnimationKeys.value.rawValue)
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /// Called when the animation timer is complete
|
|
|
|
- @objc func animationDidComplete(withTimer timer: Timer) {
|
|
|
|
- (timer.userInfo as? AnimationCompletion)?()
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-extension UICircularRing {
|
|
|
|
- /// Helper enum for animation key
|
|
|
|
- enum AnimationKeys: String {
|
|
|
|
- case value
|
|
|
|
- }
|
|
|
|
-}
|
|
|