47 Commits

Author SHA1 Message Date
Kaan Dedeoglu fb5f616d82 Merge pull request #39 from pakmee/swift2.3
converted to swift 2.3 and fixed auto correct with context
2016-09-06 18:50:21 +03:00
Jonathan Siao aa4edacabf cleaned spaces 2016-09-05 14:09:57 +04:00
Jonathan Siao 6475bc0a58 converted to swift 2.3 and fixed auto correct with context 2016-09-05 14:02:15 +04:00
Kaan Dedeoglu edeba04ff9 Merge pull request #36 from Morbix/patch-3
Update README.md
2016-07-26 14:07:14 +03:00
Kaan Dedeoglu f185b1f33d Merge pull request #34 from Morbix/patch-1
Adding CocoaPods badges
2016-07-26 14:06:58 +03:00
Henrique Morbin 79bf0d3457 Update README.md
Fix
2016-07-25 20:51:16 -03:00
Kaan Dedeoglu 4f2cfc1687 Merge pull request #35 from Morbix/patch-2
Update README.md
2016-07-26 01:11:40 +03:00
Henrique Morbin 174357710b Update README.md 2016-07-25 17:06:51 -03:00
Henrique Morbin f10c403fc2 Adding CocoaPods badges 2016-07-25 17:04:05 -03:00
Kaan Dedeoglu 094a5a64e5 bump version to 1.4.1 2016-07-21 12:36:01 +03:00
Kaan Dedeoglu 3c38489154 clear the current completionBlock variable before calling it. fixes #31 2016-07-21 12:35:08 +03:00
Kaan Dedeoglu 1d9ab519f1 Merge pull request #26 from stevekim0417/master
Master
2016-06-23 17:56:26 +03:00
Macbook 60d95709c7 Merge remote-tracking branch 'kaandedeoglu/master' 2016-06-23 23:49:16 +09:00
Macbook 0e95379f6d flag
It is not called the completion block after doing cancel.
2016-06-23 23:44:18 +09:00
Kaan Dedeoglu 840ae1fcbc Clean up and refactoring 2016-06-21 00:29:41 +03:00
Steve Kim 36160ed532 issue : not calling completion block after doing cancel
It works fine after doing cancel.
2016-05-29 16:04:06 +09:00
Kaan Dedeoglu d96c0a95a3 Merge pull request #19 from aborren/master
Changed angle to Double instead of Int
2016-04-06 14:04:03 +03:00
Dan Isacson e9d2ffa247 update podspec to 1.4.0 2016-04-04 14:35:55 +02:00
Dan Isacson 0b46df2ce7 Changed angle to use Double instead of Int. This change was made in order to make slow animations smoother. 2016-04-04 13:30:47 +02:00
Kaan Dedeoglu e5b6e5caf9 Avoid var parameters - which will be deprecated with Swift 3.0 2016-03-28 00:25:51 +03:00
Kaan Dedeoglu fee2df5cfd Update Podspec to 1.3.4 2016-03-27 13:46:21 +03:00
Kaan Dedeoglu 5726166d3d Merge pull request #18 from maltalef/master
Added colorLerpMode
2016-03-27 13:45:05 +03:00
Matias Altalef 55f1893864 Added colorLerpMode (default false): when true, it's filled with a solid color that changes with progress (only if there's more than one) 2016-03-26 18:02:19 -03:00
Kaan Dedeoglu bf388402fd Merge pull request #17 from ReadmeCritic/master
Correct the spelling of CocoaPods in README
2016-02-12 21:22:31 +02:00
ReadmeCritic f814654106 Correct the spelling of CocoaPods in README 2016-02-12 11:17:11 -08:00
Kaan Dedeoglu eeacb59a55 Make sure the presentationLayer is non-nil before trying to pause animations. Update to version 1.3.3 2016-01-27 12:07:46 +02:00
Kaan Dedeoglu ee5890b8ce Update Podspec to 1.3.2 2016-01-10 11:37:53 +02:00
Kaan Dedeoglu d116d7c380 Fix bug related to animations with progressInsideFillColor. Fix indentation 2016-01-10 11:34:56 +02:00
Kaan Dedeoglu 2ec2d6f783 Add gif to README 2015-11-12 14:39:40 +02:00
Kaan Dedeoglu 02dccb0804 Update podspec to 1.3.1 2015-11-11 13:57:45 +02:00
Kaan Dedeoglu 74c2c3c8d7 Update README.md 2015-11-11 13:56:37 +02:00
Kaan Dedeoglu f9194ec712 Add relativeDuration default value to animate methods to specify if the duration is global or specific to current angle 2015-11-11 13:52:04 +02:00
Kaan Dedeoglu 54e24f1053 Update podspec to 1.3 2015-10-16 03:38:16 +03:00
Kaan Dedeoglu 2385466f70 Update example animation call to restart it everytime the button is hit. 2015-10-16 03:37:19 +03:00
Kaan Dedeoglu ab6a4fd165 Some fixes for resizing behavior, invalidate gradient and gradient location caches when affecting properties change 2015-10-16 03:29:51 +03:00
Kaan Dedeoglu 5fe36abfe2 modify podspec for v1.2 2015-09-16 20:37:56 +03:00
Kaan Dedeoglu 466be89f6d Merge pull request #10 from kaandedeoglu/swift2.0
Swift2.0
2015-09-16 20:30:09 +03:00
Kaan Dedeoglu b9f8a3e46e Update README.md 2015-09-15 19:26:58 +03:00
Kaan Dedeoglu b9cdde76e5 Reorganize some properties, add description about progressInsideFillColor 2015-09-11 11:09:44 +03:00
Kaan Dedeoglu f7959fdd54 Add note about IBInspectable and IBDesignable support 2015-09-10 23:10:43 +03:00
Kaan Dedeoglu c01c9d9b59 Add progressInsideFillColor IBInspectable property 2015-09-10 23:06:37 +03:00
Kaan Dedeoglu 125bfcc5a4 Beta 7 fixes 2015-08-25 02:47:15 +03:00
Kaan Dedeoglu e823e29b2e Merge branch 'master' of https://github.com/kaandedeoglu/KDCircularProgress into swift2.0 2015-08-17 20:01:54 +03:00
Kaan Dedeoglu ee6a46abd3 Make IBOutletColor1-2-3 properties private. 2015-07-01 12:28:50 +03:00
Kaan Dedeoglu a90c94254e Swift 2.0 changes 2015-06-28 11:43:44 +03:00
Kaan Dedeoglu 9fb2796b70 Update README.md 2015-06-25 17:38:31 +03:00
Kaan Dedeoglu 58cbb4a4a6 Update Podspec, push 1.1 to pods 2015-06-25 17:35:14 +03:00
7 changed files with 290 additions and 141 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'KDCircularProgress'
s.version = '1.0'
s.version = '1.4.1'
s.license = 'MIT'
s.summary = 'A circular progress view with gradients written in Swift'
s.homepage = 'https://github.com/kaandedeoglu/KDCircularProgress'
@@ -11,4 +11,4 @@ Pod::Spec.new do |s|
s.source_files = 'KDCircularProgress/*.swift'
s.requires_arc = true
end
end
+225 -117
View File
@@ -13,20 +13,20 @@ public enum KDCircularProgressGlowMode {
}
@IBDesignable
public class KDCircularProgress: UIView {
public class KDCircularProgress: UIView,CAAnimationDelegate {
private struct ConversionFunctions {
static func DegreesToRadians (value:CGFloat) -> CGFloat {
private struct Conversion {
static func degreesToRadians (value:CGFloat) -> CGFloat {
return value * CGFloat(M_PI) / 180.0
}
static func RadiansToDegrees (value:CGFloat) -> CGFloat {
static func radiansToDegrees (value:CGFloat) -> CGFloat {
return value * 180.0 / CGFloat(M_PI)
}
}
private struct UtilityFunctions {
static func Clamp<T: Comparable>(value: T, minMax: (T, T)) -> T {
private struct Utility {
static func clamp<T: Comparable>(value: T, minMax: (T, T)) -> T {
let (min, max) = minMax
if value < min {
return min
@@ -37,20 +37,42 @@ public class KDCircularProgress: UIView {
}
}
static func Mod(value: Int, range: Int, minMax: (Int, Int)) -> Int {
static func inverseLerp(value: CGFloat, minMax: (CGFloat, CGFloat)) -> CGFloat {
return (value - minMax.0) / (minMax.1 - minMax.0)
}
static func lerp(value: CGFloat, minMax: (CGFloat, CGFloat)) -> CGFloat {
return (minMax.1 - minMax.0) * value + minMax.0
}
static func colorLerp(value: CGFloat, minMax: (UIColor, UIColor)) -> UIColor {
let clampedValue = clamp(value, minMax: (0, 1))
let zero: CGFloat = 0
var (r0, g0, b0, a0) = (zero, zero, zero, zero)
minMax.0.getRed(&r0, green: &g0, blue: &b0, alpha: &a0)
var (r1, g1, b1, a1) = (zero, zero, zero, zero)
minMax.1.getRed(&r1, green: &g1, blue: &b1, alpha: &a1)
return UIColor(red: lerp(clampedValue, minMax: (r0, r1)), green: lerp(clampedValue, minMax: (g0, g1)), blue: lerp(clampedValue, minMax: (b0, b1)), alpha: lerp(clampedValue, minMax: (a0, a1)))
}
static func mod(value: Double, range: Double, minMax: (Double, Double)) -> Double {
let (min, max) = minMax
assert(abs(range) <= abs(max - min), "range should be <= than the interval")
if value >= min && value <= max {
return value
} else if value < min {
return Mod(value + range, range: range, minMax: minMax)
return mod(value + range, range: range, minMax: minMax)
} else {
return Mod(value - range, range: range, minMax: minMax)
return mod(value - range, range: range, minMax: minMax)
}
}
}
private var progressLayer: KDCircularProgressViewLayer! {
private var progressLayer: KDCircularProgressViewLayer {
get {
return layer as! KDCircularProgressViewLayer
}
@@ -62,7 +84,7 @@ public class KDCircularProgress: UIView {
}
}
@IBInspectable public var angle: Int = 0 {
@IBInspectable public var angle: Double = 0 {
didSet {
if self.isAnimating() {
self.pauseAnimation()
@@ -71,9 +93,10 @@ public class KDCircularProgress: UIView {
}
}
@IBInspectable public var startAngle: Int = 0 {
@IBInspectable public var startAngle: Double = 0 {
didSet {
progressLayer.startAngle = UtilityFunctions.Mod(startAngle, range: 360, minMax: (0,360))
startAngle = Utility.mod(startAngle, range: 360, minMax: (0, 360))
progressLayer.startAngle = startAngle
progressLayer.setNeedsDisplay()
}
}
@@ -91,6 +114,12 @@ public class KDCircularProgress: UIView {
}
}
@IBInspectable public var lerpColorMode: Bool = false {
didSet {
progressLayer.lerpColorMode = lerpColorMode
}
}
@IBInspectable public var gradientRotateSpeed: CGFloat = 0 {
didSet {
progressLayer.gradientRotateSpeed = gradientRotateSpeed
@@ -99,7 +128,8 @@ public class KDCircularProgress: UIView {
@IBInspectable public var glowAmount: CGFloat = 1.0 {//Between 0 and 1
didSet {
progressLayer.glowAmount = UtilityFunctions.Clamp(glowAmount, minMax: (0, 1))
glowAmount = Utility.clamp(glowAmount, minMax: (0, 1))
progressLayer.glowAmount = glowAmount
}
}
@@ -111,25 +141,35 @@ public class KDCircularProgress: UIView {
@IBInspectable public var progressThickness: CGFloat = 0.4 {//Between 0 and 1
didSet {
progressThickness = UtilityFunctions.Clamp(progressThickness, minMax: (0, 1))
progressThickness = Utility.clamp(progressThickness, minMax: (0, 1))
progressLayer.progressThickness = progressThickness/2
}
}
@IBInspectable public var trackThickness: CGFloat = 0.5 {//Between 0 and 1
didSet {
trackThickness = UtilityFunctions.Clamp(trackThickness, minMax: (0, 1))
trackThickness = Utility.clamp(trackThickness, minMax: (0, 1))
progressLayer.trackThickness = trackThickness/2
}
}
@IBInspectable public var trackColor: UIColor = UIColor.blackColor() {
@IBInspectable public var trackColor: UIColor = .blackColor() {
didSet {
progressLayer.trackColor = trackColor
progressLayer.setNeedsDisplay()
}
}
@IBInspectable public var progressInsideFillColor: UIColor? = nil {
didSet {
if let color = progressInsideFillColor {
progressLayer.progressInsideFillColor = color
} else {
progressLayer.progressInsideFillColor = .clearColor()
}
}
}
@IBInspectable public var progressColors: [UIColor]! {
get {
return progressLayer.colorsArray
@@ -142,10 +182,9 @@ public class KDCircularProgress: UIView {
//These are used only from the Interface-Builder. Changing these from code will have no effect.
//Also IB colors are limited to 3, whereas programatically we can have an arbitrary number of them.
@IBInspectable public var IBColor1: UIColor?
@IBInspectable public var IBColor2: UIColor?
@IBInspectable public var IBColor3: UIColor?
@objc @IBInspectable private var IBColor1: UIColor?
@objc @IBInspectable private var IBColor2: UIColor?
@objc @IBInspectable private var IBColor3: UIColor?
private var animationCompletionBlock: ((Bool) -> Void)?
@@ -161,36 +200,42 @@ public class KDCircularProgress: UIView {
self.init(frame: frame)
setColors(colors)
}
required public init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setTranslatesAutoresizingMaskIntoConstraints(false)
userInteractionEnabled = false
setInitialValues()
super.init(coder: aDecoder)!
translatesAutoresizingMaskIntoConstraints = false
userInteractionEnabled = false
setInitialValues()
refreshValues()
}
}
public override func awakeFromNib() {
checkAndSetIBColors()
}
override public class func layerClass() -> AnyClass {
return KDCircularProgressViewLayer.self
}
public override func layoutSubviews() {
super.layoutSubviews()
radius = (frame.size.width/2.0) * 0.8
}
private func setInitialValues() {
radius = (frame.size.width/2.0) * 0.8 //We always apply a 20% padding, stopping glows from being clipped
backgroundColor = .clearColor()
setColors(UIColor.whiteColor(), UIColor.redColor())
setColors(.whiteColor(), .cyanColor())
}
private func refreshValues() {
progressLayer.angle = angle
progressLayer.startAngle = UtilityFunctions.Mod(startAngle, range: 360, minMax: (0,360))
progressLayer.startAngle = startAngle
progressLayer.clockwise = clockwise
progressLayer.roundedCorners = roundedCorners
progressLayer.lerpColorMode = lerpColorMode
progressLayer.gradientRotateSpeed = gradientRotateSpeed
progressLayer.glowAmount = UtilityFunctions.Clamp(glowAmount, minMax: (0, 1))
progressLayer.glowAmount = glowAmount
progressLayer.glowMode = glowMode
progressLayer.progressThickness = progressThickness/2
progressLayer.trackColor = trackColor
@@ -198,8 +243,8 @@ public class KDCircularProgress: UIView {
}
private func checkAndSetIBColors() {
let nonNilColors = [IBColor1, IBColor2, IBColor3].filter { $0 != nil}.map { $0! }
if nonNilColors.count > 0 {
let nonNilColors = [IBColor1, IBColor2, IBColor3].flatMap { $0 }
if !nonNilColors.isEmpty {
setColors(nonNilColors)
}
}
@@ -213,15 +258,24 @@ public class KDCircularProgress: UIView {
progressLayer.setNeedsDisplay()
}
public func animateFromAngle(fromAngle: Int, toAngle: Int, duration: NSTimeInterval, completion: ((Bool) -> Void)?) {
public func animateFromAngle(fromAngle: Double, toAngle: Double, duration: NSTimeInterval, relativeDuration: Bool = true, completion: ((Bool) -> Void)?) {
if isAnimating() {
pauseAnimation()
}
let animationDuration: NSTimeInterval
if relativeDuration {
animationDuration = duration
} else {
let traveledAngle = Utility.mod(toAngle - fromAngle, range: 360, minMax: (0, 360))
let scaledDuration = (NSTimeInterval(traveledAngle) * duration) / 360
animationDuration = scaledDuration
}
let animation = CABasicAnimation(keyPath: "angle")
animation.fromValue = fromAngle
animation.toValue = toAngle
animation.duration = duration
animation.duration = animationDuration
animation.delegate = self
angle = toAngle
animationCompletionBlock = completion
@@ -229,15 +283,15 @@ public class KDCircularProgress: UIView {
progressLayer.addAnimation(animation, forKey: "angle")
}
public func animateToAngle(toAngle: Int, duration: NSTimeInterval, completion: ((Bool) -> Void)?) {
public func animateToAngle(toAngle: Double, duration: NSTimeInterval, relativeDuration: Bool = true, completion: ((Bool) -> Void)?) {
if isAnimating() {
pauseAnimation()
}
animateFromAngle(angle, toAngle: toAngle, duration: duration, completion: completion)
animateFromAngle(angle, toAngle: toAngle, duration: duration, relativeDuration: relativeDuration, completion: completion)
}
public func pauseAnimation() {
let presentationLayer = progressLayer.presentationLayer() as! KDCircularProgressViewLayer
guard let presentationLayer = progressLayer.presentationLayer() else { return }
let currentValue = presentationLayer.angle
progressLayer.removeAllAnimations()
animationCompletionBlock = nil
@@ -245,7 +299,7 @@ public class KDCircularProgress: UIView {
}
public func stopAnimation() {
let presentationLayer = progressLayer.presentationLayer() as! KDCircularProgressViewLayer
animationCompletionBlock = nil
progressLayer.removeAllAnimations()
angle = 0
}
@@ -254,10 +308,13 @@ public class KDCircularProgress: UIView {
return progressLayer.animationForKey("angle") != nil
}
override public func animationDidStop(anim: CAAnimation!, finished flag: Bool) {
public func animationDidStop(anim: CAAnimation, finished flag: Bool) {
if let completionBlock = animationCompletionBlock {
if flag {
animationCompletionBlock = nil
}
completionBlock(flag)
animationCompletionBlock = nil
}
}
@@ -281,29 +338,44 @@ public class KDCircularProgress: UIView {
}
private class KDCircularProgressViewLayer: CALayer {
@NSManaged var angle: Int
var radius: CGFloat!
var startAngle: Int!
var clockwise: Bool!
@NSManaged var angle: Double
var radius: CGFloat! {
didSet {
invalidateGradientCache()
}
}
var startAngle: Double!
var clockwise: Bool! {
didSet {
if clockwise != oldValue {
invalidateGradientCache()
}
}
}
var roundedCorners: Bool!
var gradientRotateSpeed: CGFloat!
var lerpColorMode: Bool!
var gradientRotateSpeed: CGFloat! {
didSet {
invalidateGradientCache()
}
}
var glowAmount: CGFloat!
var glowMode: KDCircularProgressGlowMode!
var progressThickness: CGFloat!
var trackThickness: CGFloat!
var trackColor: UIColor!
var progressInsideFillColor: UIColor = UIColor.clearColor()
var colorsArray: [UIColor]! {
didSet {
gradientCache = nil
locationsCache = nil
invalidateGradientCache()
}
}
var gradientCache: CGGradientRef?
var locationsCache: [CGFloat]?
private var gradientCache: CGGradientRef?
private var locationsCache: [CGFloat]?
struct GlowConstants {
static let sizeToGlowRatio: CGFloat = 0.00015
static func glowAmountForAngle(angle: Int, glowAmount: CGFloat, glowMode: KDCircularProgressGlowMode, size: CGFloat) -> CGFloat {
private struct GlowConstants {
private static let sizeToGlowRatio: CGFloat = 0.00015
static func glowAmountForAngle(angle: Double, glowAmount: CGFloat, glowMode: KDCircularProgressGlowMode, size: CGFloat) -> CGFloat {
switch glowMode {
case .Forward:
return CGFloat(angle) * size * sizeToGlowRatio * glowAmount
@@ -317,11 +389,11 @@ public class KDCircularProgress: UIView {
}
}
override class func needsDisplayForKey(key: String!) -> Bool {
override class func needsDisplayForKey(key: String) -> Bool {
return key == "angle" ? true : super.needsDisplayForKey(key)
}
override init!(layer: AnyObject!) {
override init(layer: AnyObject) {
super.init(layer: layer)
let progressLayer = layer as! KDCircularProgressViewLayer
radius = progressLayer.radius
@@ -329,6 +401,7 @@ public class KDCircularProgress: UIView {
startAngle = progressLayer.startAngle
clockwise = progressLayer.clockwise
roundedCorners = progressLayer.roundedCorners
lerpColorMode = progressLayer.lerpColorMode
gradientRotateSpeed = progressLayer.gradientRotateSpeed
glowAmount = progressLayer.glowAmount
glowMode = progressLayer.glowMode
@@ -336,126 +409,161 @@ public class KDCircularProgress: UIView {
trackThickness = progressLayer.trackThickness
trackColor = progressLayer.trackColor
colorsArray = progressLayer.colorsArray
progressInsideFillColor = progressLayer.progressInsideFillColor
}
override init!() {
override init() {
super.init()
}
required init(coder aDecoder: NSCoder) {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func drawInContext(ctx: CGContext!) {
override func drawInContext(ctx: CGContext) {
UIGraphicsPushContext(ctx)
let rect = bounds
let size = rect.size
let trackLineWidth: CGFloat = radius * trackThickness
let size = bounds.size
let width = size.width
let height = size.height
let trackLineWidth = radius * trackThickness
let progressLineWidth = radius * progressThickness
let arcRadius = max(radius - trackLineWidth/2, radius - progressLineWidth/2)
CGContextAddArc(ctx, CGFloat(size.width/2.0), CGFloat(size.height/2.0), arcRadius, 0, CGFloat(M_PI * 2), 0)
CGContextAddArc(ctx, width/2.0, height/2.0, arcRadius, 0, CGFloat(M_PI * 2), 0)
trackColor.set()
CGContextSetStrokeColorWithColor(ctx, trackColor.CGColor)
CGContextSetFillColorWithColor(ctx, progressInsideFillColor.CGColor)
CGContextSetLineWidth(ctx, trackLineWidth)
CGContextSetLineCap(ctx, kCGLineCapButt)
CGContextDrawPath(ctx, kCGPathStroke)
CGContextSetLineCap(ctx, CGLineCap.Butt)
CGContextDrawPath(ctx, .FillStroke)
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
let imageCtx = UIGraphicsGetCurrentContext()
let reducedAngle = UtilityFunctions.Mod(angle, range: 360, minMax: (0, 360))
let fromAngle = ConversionFunctions.DegreesToRadians(CGFloat(-startAngle))
let toAngle = ConversionFunctions.DegreesToRadians(CGFloat((clockwise == true ? -reducedAngle : reducedAngle) - startAngle))
CGContextAddArc(imageCtx, CGFloat(size.width/2.0),CGFloat(size.height/2.0), arcRadius, fromAngle, toAngle, clockwise == true ? 1 : 0)
let glowValue = GlowConstants.glowAmountForAngle(reducedAngle, glowAmount: glowAmount, glowMode: glowMode, size: size.width)
if glowValue > 0 {
CGContextSetShadowWithColor(imageCtx, CGSizeZero, glowValue, UIColor.blackColor().CGColor)
let reducedAngle = Utility.mod(angle, range: 360, minMax: (0, 360))
if let imageCtx = UIGraphicsGetCurrentContext(){
let fromAngle = Conversion.degreesToRadians(CGFloat(-startAngle))
let toAngle = Conversion.degreesToRadians(CGFloat((clockwise == true ? -reducedAngle : reducedAngle) - startAngle))
CGContextAddArc(imageCtx, width/2.0, height/2.0, arcRadius, fromAngle, toAngle, clockwise == true ? 1 : 0)
let glowValue = GlowConstants.glowAmountForAngle(reducedAngle, glowAmount: glowAmount, glowMode: glowMode, size: width)
if glowValue > 0 {
CGContextSetShadowWithColor(imageCtx, CGSizeZero, glowValue, UIColor.blackColor().CGColor)
}
CGContextSetLineCap(imageCtx, roundedCorners == true ? .Round : .Butt)
CGContextSetLineWidth(imageCtx, progressLineWidth)
CGContextDrawPath(imageCtx, .Stroke)
guard let currentGraphicsContext = UIGraphicsGetCurrentContext() else{
return
}
if let drawMask: CGImageRef = CGBitmapContextCreateImage(currentGraphicsContext){
UIGraphicsEndImageContext()
CGContextSaveGState(ctx)
CGContextClipToMask(ctx, bounds, drawMask)
}else{
UIGraphicsEndImageContext()
}
}
CGContextSetLineCap(imageCtx, roundedCorners == true ? kCGLineCapRound : kCGLineCapButt)
CGContextSetLineWidth(imageCtx, progressLineWidth)
CGContextDrawPath(imageCtx, kCGPathStroke)
let drawMask: CGImageRef = CGBitmapContextCreateImage(UIGraphicsGetCurrentContext())
UIGraphicsEndImageContext()
CGContextSaveGState(ctx)
CGContextClipToMask(ctx, bounds, drawMask)
//Gradient - Fill
if colorsArray.count > 1 {
var componentsArray: [CGFloat] = []
let rgbColorsArray: [UIColor] = colorsArray.map {c in // Make sure every color in colors array is in RGB color space
if CGColorGetNumberOfComponents(c.CGColor) == 2 {
let whiteValue = CGColorGetComponents(c.CGColor)[0]
if !lerpColorMode && colorsArray.count > 1 {
let rgbColorsArray: [UIColor] = colorsArray.map { color in // Make sure every color in colors array is in RGB color space
if CGColorGetNumberOfComponents(color.CGColor) == 2 {
let whiteValue = CGColorGetComponents(color.CGColor)[0]
return UIColor(red: whiteValue, green: whiteValue, blue: whiteValue, alpha: 1.0)
} else {
return c
return color
}
}
for color in rgbColorsArray {
let colorComponents: UnsafePointer<CGFloat> = CGColorGetComponents(color.CGColor)
componentsArray.extend([colorComponents[0],colorComponents[1],colorComponents[2],1.0])
let componentsArray = rgbColorsArray.flatMap { color -> [CGFloat] in
let components: UnsafePointer<CGFloat> = CGColorGetComponents(color.CGColor)
return [components[0], components[1], components[2], 1.0]
}
drawGradientWithContext(ctx, componentsArray: componentsArray)
} else {
if colorsArray.count == 1 {
fillRectWithContext(ctx, color: colorsArray[0])
var color: UIColor?
if colorsArray.isEmpty {
color = UIColor.whiteColor()
} else if colorsArray.count == 1 {
color = colorsArray[0]
} else {
fillRectWithContext(ctx, color: UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0))
// lerpColorMode is true
let t = CGFloat(reducedAngle) / 360
let steps = colorsArray.count - 1
let step = 1 / CGFloat(steps)
for i in 1...steps {
let fi = CGFloat(i)
if (t <= fi * step || i == steps) {
let colorT = Utility.inverseLerp(t, minMax: ((fi - 1) * step, fi * step))
color = Utility.colorLerp(colorT, minMax: (colorsArray[i - 1], colorsArray[i]))
break
}
}
}
if let color = color {
fillRectWithContext(ctx, color: color)
}
}
CGContextRestoreGState(ctx)
UIGraphicsPopContext()
}
func fillRectWithContext(ctx: CGContext!, color: UIColor) {
private func fillRectWithContext(ctx: CGContext!, color: UIColor) {
CGContextSetFillColorWithColor(ctx, color.CGColor)
CGContextFillRect(ctx, bounds)
}
func drawGradientWithContext(ctx: CGContext!, componentsArray: [CGFloat]) {
private func drawGradientWithContext(ctx: CGContext!, componentsArray: [CGFloat]) {
let baseSpace = CGColorSpaceCreateDeviceRGB()
let locations = locationsCache ?? gradientLocationsFromColorCount(componentsArray.count/4, gradientWidth: bounds.size.width)
let locations = locationsCache ?? gradientLocationsForColorCount(componentsArray.count/4, gradientWidth: bounds.size.width)
let gradient: CGGradient
if let g = self.gradientCache {
gradient = g
if let cachedGradient = gradientCache {
gradient = cachedGradient
} else {
let g = CGGradientCreateWithColorComponents(baseSpace, componentsArray, locations,componentsArray.count / 4)
self.gradientCache = g
gradient = g
guard let cachedGradient = CGGradientCreateWithColorComponents(baseSpace, componentsArray, locations,componentsArray.count / 4) else {
return
}
gradientCache = cachedGradient
gradient = cachedGradient
}
let halfX = bounds.size.width/2.0
let halfX = bounds.size.width / 2.0
let floatPi = CGFloat(M_PI)
let rotateSpeed = clockwise == true ? gradientRotateSpeed : gradientRotateSpeed * -1
let angleInRadians = ConversionFunctions.DegreesToRadians(rotateSpeed * CGFloat(angle) - 90)
var oppositeAngle = angleInRadians > floatPi ? angleInRadians - floatPi : angleInRadians + floatPi
let angleInRadians = Conversion.degreesToRadians(rotateSpeed * CGFloat(angle) - 90)
let oppositeAngle = angleInRadians > floatPi ? angleInRadians - floatPi : angleInRadians + floatPi
let startPoint = CGPoint(x: (cos(angleInRadians) * halfX) + halfX, y: (sin(angleInRadians) * halfX) + halfX)
let endPoint = CGPoint(x: (cos(oppositeAngle) * halfX) + halfX, y: (sin(oppositeAngle) * halfX) + halfX)
CGContextDrawLinearGradient(ctx, gradient, startPoint, endPoint, 0)
CGContextDrawLinearGradient(ctx, gradient, startPoint, endPoint, .DrawsBeforeStartLocation)
}
func gradientLocationsFromColorCount(colorCount: Int, gradientWidth: CGFloat) -> [CGFloat] {
private func gradientLocationsForColorCount(colorCount: Int, gradientWidth: CGFloat) -> [CGFloat] {
if colorCount == 0 || gradientWidth == 0 {
return []
} else {
var locationsArray: [CGFloat] = []
let progressLineWidth = radius * progressThickness
let firstPoint = gradientWidth/2 - (radius - progressLineWidth/2)
let increment = (gradientWidth - (2*firstPoint))/CGFloat(colorCount - 1)
for i in 0..<colorCount {
locationsArray.append(firstPoint + (CGFloat(i) * increment))
}
assert(locationsArray.count == colorCount, "color counts should be equal")
let locationsArray = (0..<colorCount).map { firstPoint + (CGFloat($0) * increment) }
let result = locationsArray.map { $0 / gradientWidth }
locationsCache = result
return result
}
}
private func invalidateGradientCache() {
gradientCache = nil
locationsCache = nil
}
}
}
@@ -168,14 +168,17 @@
BC9E757E1A8CE4A500B1DF3D /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0700;
LastUpgradeCheck = 0610;
ORGANIZATIONNAME = "Kaan Dedeoglu";
TargetAttributes = {
BC9E75851A8CE4A500B1DF3D = {
CreatedOnToolsVersion = 6.1.1;
LastSwiftMigration = 0800;
};
BC9E759A1A8CE4A500B1DF3D = {
CreatedOnToolsVersion = 6.1.1;
LastSwiftMigration = 0800;
TestTargetID = BC9E75851A8CE4A500B1DF3D;
};
};
@@ -352,6 +355,7 @@
INFOPLIST_FILE = KDCircularProgressExample/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 2.3;
};
name = Debug;
};
@@ -362,6 +366,7 @@
INFOPLIST_FILE = KDCircularProgressExample/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 2.3;
};
name = Release;
};
@@ -380,6 +385,7 @@
INFOPLIST_FILE = KDCircularProgressExampleTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 2.3;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/KDCircularProgressExample.app/KDCircularProgressExample";
};
name = Debug;
@@ -395,6 +401,7 @@
INFOPLIST_FILE = KDCircularProgressExampleTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 2.3;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/KDCircularProgressExample.app/KDCircularProgressExample";
};
name = Release;
@@ -418,6 +425,7 @@
BC9E75A71A8CE4A500B1DF3D /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
BC9E75A81A8CE4A500B1DF3D /* Build configuration list for PBXNativeTarget "KDCircularProgressExampleTests" */ = {
isa = XCConfigurationList;
@@ -426,6 +434,7 @@
BC9E75AA1A8CE4A500B1DF3D /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
@@ -1,7 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="7706" systemVersion="14D136" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" initialViewController="vXZ-lx-hvc">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="8191" systemVersion="15A284" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" initialViewController="vXZ-lx-hvc">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="7703"/>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="8154"/>
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
</dependencies>
<scenes>
@@ -19,6 +20,7 @@
<subviews>
<slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" minValue="0.0" maxValue="360" translatesAutoresizingMaskIntoConstraints="NO" id="BHN-x6-Zsx">
<rect key="frame" x="61" y="478" width="198" height="31"/>
<animations/>
<constraints>
<constraint firstAttribute="height" constant="30" id="6DH-hv-h4S"/>
<constraint firstAttribute="width" constant="194" id="oTI-kD-sOa"/>
@@ -29,6 +31,7 @@
</slider>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="5M2-Yi-RuL">
<rect key="frame" x="112" y="523" width="96" height="30"/>
<animations/>
<constraints>
<constraint firstAttribute="width" constant="96" id="T9z-qf-POZ"/>
<constraint firstAttribute="height" constant="30" id="h5a-bV-EMl"/>
@@ -42,6 +45,7 @@
</button>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="wev-Tz-pfW" customClass="KDCircularProgress" customModule="KDCircularProgressExample" customModuleProvider="target">
<rect key="frame" x="60" y="20" width="200" height="200"/>
<animations/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstAttribute="width" constant="200" id="191-H5-EIc"/>
@@ -80,6 +84,7 @@
</userDefinedRuntimeAttributes>
</view>
</subviews>
<animations/>
<color key="backgroundColor" red="0.4736600907821229" green="0.4736600907821229" blue="0.4736600907821229" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstAttribute="centerX" secondItem="BHN-x6-Zsx" secondAttribute="centerX" id="Arq-5P-YRp"/>
@@ -96,9 +101,4 @@
<point key="canvasLocation" x="410" y="396"/>
</scene>
</scenes>
<simulatedMetricsContainer key="defaultSimulatedMetrics">
<simulatedStatusBarMetrics key="statusBar"/>
<simulatedOrientationMetrics key="orientation"/>
<simulatedScreenMetrics key="destination" type="retina4"/>
</simulatedMetricsContainer>
</document>
@@ -9,7 +9,6 @@
import UIKit
class ViewController: UIViewController {
var progress: KDCircularProgress!
override func viewDidLoad() {
@@ -31,15 +30,15 @@ class ViewController: UIViewController {
}
@IBAction func sliderDidChangeValue(sender: UISlider) {
progress.angle = Int(sender.value)
progress.angle = Double(sender.value)
}
@IBAction func animateButtonTapped(sender: UIButton) {
progress.animateToAngle(360, duration: 5) { completed in
progress.animateFromAngle(0, toAngle: 360, duration: 5) { completed in
if completed {
println("animation stopped, completed")
print("animation stopped, completed")
} else {
println("animation stopped, was interrupted")
print("animation stopped, was interrupted")
}
}
}
+43 -10
View File
@@ -1,11 +1,21 @@
# KDCircularProgress
`KDCircularProgress` is a circular progress view written in Swift. It makes it possible to have gradients in the progress view, along with glows and animations. Here's an example
[![Version](https://img.shields.io/cocoapods/v/KDCircularProgress.svg?style=flat)](http://cocoapods.org/pods/KDCircularProgress)
[![License](https://img.shields.io/cocoapods/l/KDCircularProgress.svg?style=flat)](http://cocoapods.org/pods/KDCircularProgress)
[![Platform](https://img.shields.io/cocoapods/p/KDCircularProgress.svg?style=flat)](http://cocoapods.org/pods/KDCircularProgress)
`KDCircularProgress` is a circular progress view written in Swift. It makes it possible to have gradients in the progress view, along with glows and animations.
KDCircularProgress also has `IBInspectable` and `IBDesignable` support, so you can configure and preview inside the `Interface Builder`.
Here's an example
[Youtube Link](http://youtu.be/iIdas72MXOg)
[![Screenshot](https://raw.githubusercontent.com/kaandedeoglu/KDCircularProgress/master/screenshot.png)](http://youtu.be/iIdas72MXOg)
![Screenshot](https://raw.githubusercontent.com/kaandedeoglu/KDCircularProgress/master/screenshot.gif)
![Screenshot](https://raw.githubusercontent.com/kaandedeoglu/KDCircularProgress/master/screenshot.png)
![Screenshot](https://raw.githubusercontent.com/kaandedeoglu/KDCircularProgress/master/screenshot2.jpg)
![Screenshot](https://raw.githubusercontent.com/kaandedeoglu/KDCircularProgress/master/screenshot3.jpg)
![Screenshot](https://raw.githubusercontent.com/kaandedeoglu/KDCircularProgress/master/screenshot4.jpg)
@@ -29,11 +39,32 @@ view.addSubview(progress)
```
## Installation
- It's on Cocoapods under the name (you guessed it!) KDCircularProgress
- It's on CocoaPods under the name (you guessed it!) KDCircularProgress
- Just drag `KDCircularProgress.swift` into your project. `Carthage` support is on To-do list.
### CocoaPods
KDCircularProgress is available through [CocoaPods](http://cocoapods.org). To install
it, simply add the following line to your Podfile:
```ruby
pod 'KDCircularProgress'
```
### Manually
Just drag `KDCircularProgress.swift` into your project.
### Carthage
`Carthage` support is on To-do list.
## Properties
####progressColors: `[UIColor]`
The colors used to generate the gradient of the progress. You can also set this using the variadic `setColors(UIColor...)` method. A gradient is used only if there is more than one color. A fill is used otherwise. The default is a white fill.
####angle: `Int`
The angle of the progress. Between 0 and 360 (inclusive). Simply change its value in order to change the visual progress of the component. Default is 0.
@@ -47,7 +78,7 @@ Clockwise if true, Counter-clockwise if false. Default is true.
When true, the ends of the progress track will be drawn with a half circle radius. Default is false.
####gradientRotateSpeed: `CGFloat`
Describes how many times the underlying gradient will turn for each full cycle of the progress. Integer values recommended. Default is 0.
Describes how many times the underlying gradient will perform a 2π rotation for each full cycle of the progress. Integer values recommended. Default is 0.
####glowAmount: `CGFloat`
The intensity of the glow. Between 0 and 1.0. Default is 1.0.
@@ -70,10 +101,10 @@ The thickness of the progress. Between 0 and 1. Default is 0.4
The thickness of the background track. Between 0 and 1. Default is 0.5
####trackColor: `UIColor`
The color of the background track. Default is black.
The color of the background track. Default is `UIColor.blackColor()`.
####progressColors: `[UIColor]`
The colors used to generate the gradient of the progress. You can also set this using the variadic setColors(UIColor...) method. A gradient is used only if there is more than one color. A fill is used otherwise. The default is a white fill.
####progressInsideFillColor: `UIColor`
The color of the center of the circle. Default is `UIColor.clearColor()`.
##Methods
```swift
@@ -93,11 +124,13 @@ public func setColors(colors: UIColor...)
Set the colors for the progress gradient.
```swift
public func animateFromAngle(fromAngle: Int, toAngle: Int, duration: NSTimeInterval, completion: ((Bool) -> Void)?)
public func animateFromAngle(fromAngle: Int, toAngle: Int, duration: NSTimeInterval, relativeDuration: Bool = true, completion: ((Bool) -> Void)?)
```
Animate the progress from an initial value to a final value, with a completion block that fires after the animation is done.
`relativeDuration` - specify if the duration is for the specific animation or is the duration that would make a full turn.
```swift
public func animateToAngle(toAngle: Int, duration: NSTimeInterval, completion: ((Bool) -> Void)?)
```
@@ -122,8 +155,8 @@ Prefering light colors in the gradients gives better results. As mentioned befor
##To-Do
- [x] Add example project
- [ ] Carthage Support
- [x] Cocoapods Support
- [ ] IBDesignable/IBInspectable support
- [x] CocoaPods Support
- [x] IBDesignable/IBInspectable support
- [ ] Adding a `progress` property as an alternative to `angle`
- [ ] Clean up
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 861 KiB