Skip to content
This repository has been archived by the owner on Jan 19, 2022. It is now read-only.

Add support for custom knob paths, and knob images #209

Merged
merged 14 commits into from
Sep 15, 2019
122 changes: 68 additions & 54 deletions src/UICircularProgressRing/UICircularRing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
import UIKit

/**

# UICircularRing

This is the base class of `UICircularProgressRing` and `UICircularTimerRing`.
Expand All @@ -35,32 +35,32 @@ import UIKit

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() }
Expand All @@ -70,14 +70,14 @@ import UIKit

/**
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
*/
Expand Down Expand Up @@ -111,6 +111,20 @@ import UIKit
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`.

Expand All @@ -124,15 +138,15 @@ import UIKit

/**
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
*/
Expand All @@ -142,15 +156,15 @@ import UIKit

/**
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
*/
Expand All @@ -162,10 +176,10 @@ import UIKit

/**
The width of the outer ring for the progres bar

## Important ##
Default = 10.0

## Author
Luis Padron
*/
Expand All @@ -175,10 +189,10 @@ import UIKit

/**
The color for the outer ring

## Important ##
Default = UIColor.gray

## Author
Luis Padron
*/
Expand All @@ -188,14 +202,14 @@ import UIKit

/**
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
*/
Expand All @@ -207,10 +221,10 @@ import UIKit

/**
The width of the inner ring for the progres bar

## Important ##
Default = 5.0

## Author
Luis Padron
*/
Expand All @@ -220,10 +234,10 @@ import UIKit

/**
The color of the inner ring for the progres bar

## Important ##
Default = UIColor.blue

## Author
Luis Padron
*/
Expand All @@ -233,12 +247,12 @@ import UIKit

/**
The spacing between the outer ring and inner ring

## Important ##
This only applies when using progressRingStyle = 1

Default = 1

## Author
Luis Padron
*/
Expand All @@ -248,12 +262,12 @@ import UIKit

/**
The style for the tip/cap of the inner ring

Type: `CGLineCap`

## Important ##
Default = CGLineCap.round

## Author
Luis Padron
*/
Expand All @@ -265,11 +279,11 @@ import UIKit

/**
The text color for the value label field

## Important ##
Default = UIColor.black


## Author
Luis Padron
*/
Expand All @@ -281,12 +295,12 @@ import UIKit
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
*/
Expand All @@ -296,10 +310,10 @@ import UIKit

/**
This returns whether or not the ring is currently animating

## Important ##
Get only property

## Author
Luis Padron
*/
Expand All @@ -310,10 +324,10 @@ import UIKit
/**
The direction the circle is drawn in
Example: true -> clockwise

## Important ##
Default = true (draw the circle clockwise)

## Author
Pete Walker
*/
Expand Down Expand Up @@ -527,10 +541,10 @@ import UIKit
/**
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) {
Expand All @@ -540,10 +554,10 @@ import UIKit
/**
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,
Expand Down
27 changes: 21 additions & 6 deletions src/UICircularProgressRing/UICircularRingLayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ class UICircularRingLayer: CAShapeLayer {
ctx.restoreGState()
}

if let knobStyle = ring.valueKnobStyle, value > minValue {
if let knobStyle = ring.valueKnobStyle, ((value > minValue) || (ring?.shouldDrawMinValueKnob ?? false)) {
let knobOffset = knobStyle.size / 2
drawValueKnob(in: ctx, origin: CGPoint(x: innerPath.currentPoint.x - knobOffset,
y: innerPath.currentPoint.y - knobOffset))
Expand All @@ -265,10 +265,11 @@ class UICircularRingLayer: CAShapeLayer {

case .bordered(let borderWidth, let borderColor):
let center: CGPoint = CGPoint(x: bounds.midX, y: bounds.midY)
let knobSize = valueKnobStyle?.size ?? 0
let offSet = max(ring.outerRingWidth, ring.innerRingWidth) / 2
+ knobSize / 4
+ borderWidth * 2
let offSet: CGFloat = {
let offset = max(ring.outerRingWidth, ring.innerRingWidth) / 2
let size = valueKnobStyle?.size ?? 0
return offset + (size / 4) + (borderWidth * 2)
}()
let outerRadius: CGFloat = min(bounds.width, bounds.height) / 2 - offSet
let borderStartAngle = ring.outerCapStyle == .butt ? ring.startAngle - borderWidth : ring.startAngle
let borderEndAngle = ring.outerCapStyle == .butt ? ring.endAngle + borderWidth : ring.endAngle
Expand Down Expand Up @@ -362,7 +363,7 @@ class UICircularRingLayer: CAShapeLayer {
context.saveGState()

let rect = CGRect(origin: origin, size: CGSize(width: knobStyle.size, height: knobStyle.size))
let knobPath = UIBezierPath(ovalIn: rect)
let knobPath = knobStyle.path.from(rect)

context.setShadow(offset: knobStyle.shadowOffset,
blur: knobStyle.shadowBlur,
Expand All @@ -374,6 +375,20 @@ class UICircularRingLayer: CAShapeLayer {
context.drawPath(using: .fill)

context.restoreGState()

if let image = knobStyle.image {
context.saveGState()

let imageRect = rect.inset(by: knobStyle.imageInsets)
if let tintColor = knobStyle.imageTintColor {
tintColor.setFill()
image.withRenderingMode(.alwaysTemplate).draw(in: imageRect)
} else {
image.draw(in: imageRect)
}

context.restoreGState()
}
}

/**
Expand Down
Loading