问题描述
注意:有一个“外环”,但我已将其设置为隐藏。
为此有两个类;下面的UICircularProgressRing
:
final public class UICircularProgressRing: UICircularRing {
// MARK: Members
/**
The delegate for the UICircularRing
## Important ##
When progress is done updating via UICircularRing.setValue(_:),the
finishedUpdatingProgressFor(_ ring: UICircularRing) will be called.
The ring will be passed to the delegate in order to keep track of
multiple ring updates if needed.
## Author
Luis padron
*/
public weak var delegate: UICircularProgressRingDelegate?
/**
The value property for the progress ring.
## Important ##
Default = 0
Must be a non-negative value. If this value falls below `minValue` it will be
clamped and set equal to `minValue`.
This cannot be used to get the value while the ring is animating,to get
current value while animating use `currentValue`.
The current value of the progress ring after animating,use startProgress(value:)
to alter the value with the option to animate and have a completion handler.
## Author
Luis padron
*/
@IBInspectable public var value: CGFloat = 0 {
didSet {
if value < minValue {
#if DEBUG
print("Warning in: \(#file):\(#line)")
print("Attempted to set a value less than minValue,value has been set to minValue.\n")
#endif
ringLayer.value = minValue
} else if value > maxValue {
#if DEBUG
print("Warning in: \(#file):\(#line)")
print("Attempted to set a value greater than maxValue,value has been set to maxValue.\n")
#endif
ringLayer.value = maxValue
} else {
ringLayer.value = value
}
}
}
/**
The current value of the progress ring
This will return the current value of the progress ring,if the ring is animating it will be updated in real time.
If the ring is not currently animating then the value returned
will be the `value` property of the ring
## Author
Luis padron
*/
public var currentValue: CGFloat? {
return isAnimating ? layer.presentation()?.value(forKey: .value) as? CGFloat : value
}
/**
The minimum value for the progress ring. ex: (0) -> 100.
## Important ##
Default = 0.0
Must be a non-negative value,the absolute value is taken when setting this property.
The `value` of the progress ring must NOT fall below `minValue` if it does the `value` property is clamped
and will be set equal to `value`,you will receive a warning message in the console.
Making this value greater than
## Author
Luis padron
*/
@IBInspectable public var minValue: CGFloat = 0.0 {
didSet { ringLayer.minValue = minValue }
}
/**
The maximum value for the progress ring. ex: 0 -> (100)
## Important ##
Default = 100.0
Must be a non-negative value,the absolute value is taken when setting this property.
Unlike the `minValue` member `value` can extend beyond `maxValue`. What happens in this case
is the inner ring will do an extra loop through the outer ring,this is not noticible however.
## Author
Luis padron
*/
@IBInspectable public var maxValue: CGFloat = 100.0 {
didSet { ringLayer.maxValue = maxValue }
}
/**
The type of animation function the ring view will use
## Important ##
Default = .easeInEaSEOut
## Author
Luis padron
*/
public var animationTimingFunction: camediatimingFunctionName = .easeInEaSEOut {
didSet { ringLayer.animationTimingFunction = animationTimingFunction }
}
/**
The formatter responsible for formatting the
value of the progress ring into a readable text string
which is then displayed in the label of the ring.
Default formatter is of type `UICircularProgressRingFormatter`.
## Author
Luis padron
*/
public var valueFormatter: UICircularRingValueFormatter = UICircularProgressRingFormatter() {
didSet { ringLayer.valueFormatter = valueFormatter }
}
/**
Typealias for the startProgress(:) method closure
*/
public typealias ProgressCompletion = (() -> Void)
/// The completion block to call after the animation is done
private var completion: ProgressCompletion?
// MARK: API
/**
Sets the current value for the progress ring,calling this method while ring is
animating will cancel the prevIoUsly set animation and start a new one.
- Parameter to: The value to be set for the progress ring
- Parameter duration: The time interval duration for the animation
- Parameter completion: The completion closure block that will be called when
animtion is finished (also called when animationDuration = 0),default is nil
## Important ##
Animation duration = 0 will cause no animation to occur,and value will instantly
be set.
## Author
Luis padron
*/
public func startProgress(to value: CGFloat,duration: TimeInterval,completion: ProgressCompletion? = nil) {
// Store the completion event locally
self.completion = completion
// call super class helper function to begin animating layer
startAnimation(duration: duration) {
self.delegate?.didFinishProgress(for: self)
self.completion?()
}
self.value = value
}
/**
Pauses the currently running animation and halts all progress.
## Important ##
This method has no effect unless called when there is a running animation.
You should call this method manually whenever the progress ring is not in an active view,for example in `viewWilldisappear` in a parent view controller.
## Author
Luis padron & Nicolai Cornelis
*/
public func pauseProgress() {
// call super class helper to stop layer animation
pauseAnimation()
delegate?.didPauseProgress(for: self)
}
/**
Continues the animation with its remaining time from where it left off before it was paused.
This method has no effect unless called when there is a paused animation.
You should call this method when you wish to resume a paused animation.
## Author
Luis padron & Nicolai Cornelis
*/
public func continueProgress() {
// call super class helper to continue layer animation
continueAnimation {
self.delegate?.didFinishProgress(for: self)
self.completion?()
}
delegate?.didContinueProgress(for: self)
}
/**
Resets the progress back to the `minValue` of the progress ring.
Does **not** perform any animations
## Author
Luis padron
*/
public func resetProgress() {
// call super class helper to reset animation layer
resetAnimation()
value = minValue
// Remove reference to the completion block
completion = nil
}
// MARK: Overrides
override func initialize() {
super.initialize()
ringLayer.ring = self
ringLayer.value = value
ringLayer.maxValue = maxValue
ringLayer.minValue = minValue
ringLayer.valueFormatter = valueFormatter
}
override func didUpdateValue(newValue: CGFloat) {
super.didUpdateValue(newValue: newValue)
delegate?.didUpdateProgressValue(for: self,to: newValue)
}
override func willdisplayLabel(label: UILabel) {
super.willdisplayLabel(label: label)
delegate?.willdisplayLabel(for: self,label)
}
}
还有它继承的 UICircularRing
:
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() }
}
/**
A toggle for showing or hiding the value knob when current value == minimum value.
If false the value knob will not be shown when current value == minimum value.
## Important ##
Default = false
## Author
Tom Knapen
*/
@IBInspectable public var shouldDrawMinValueKnob: Bool = false {
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() }
}
/**
Determines if the progress ring should animate in reverse
## Important ##
Default = false
*/
open var isReverse: Bool = false {
didSet { ringLayer.isReverse = isReverse }
}
// 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 `ringStyle` = `.inside`
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,selector: #selector(animationDidComplete),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,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
}
}
我现在要写的是实现一个基于渐变的指示器,整个圆圈是可见的,然后在加载时填充。有点卡在这里;不知道什么是正确的方法!
解决方法
暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!
如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。
小编邮箱:dio#foxmail.com (将#修改为@)