30 Commits

Author SHA1 Message Date
Kaan Dedeoglu 5e2d0a8d5a Update README.md 2016-09-11 20:42:57 +03:00
Kaan Dedeoglu 9cee5ecd92 Update README.md 2016-09-11 20:42:18 +03:00
Kaan Dedeoglu 087361dc92 Merge branch 'swift3' 2016-09-11 20:38:27 +03:00
Kaan Dedeoglu 427d5d5592 Update for Xcode 8 beta 6. 2016-08-17 11:02:46 +03: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 1e16600001 cherry pick 3c38489 into our Swift3 branch. 2016-07-21 13:10:55 +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 d15df140c4 Update to recommended project settings. 2016-06-21 01:47:13 +03:00
Kaan Dedeoglu 63ae2ea28d Update Podspec to 1.5.0-beta1 2016-06-21 01:24:13 +03:00
Kaan Dedeoglu 69a0cd0640 Update to Swift 3.0 and Xcode 8 2016-06-21 01:19:59 +03: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
10 changed files with 253 additions and 147 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'KDCircularProgress'
s.version = '1.3.3'
s.version = '1.5.0'
s.license = 'MIT'
s.summary = 'A circular progress view with gradients written in Swift'
s.homepage = 'https://github.com/kaandedeoglu/KDCircularProgress'
+180 -121
View File
@@ -9,24 +9,20 @@
import UIKit
public enum KDCircularProgressGlowMode {
case Forward, Reverse, Constant, NoGlow
case forward, reverse, constant, noGlow
}
@IBDesignable
public class KDCircularProgress: UIView {
public class KDCircularProgress: UIView, CAAnimationDelegate {
private struct ConversionFunctions {
static func DegreesToRadians (value:CGFloat) -> CGFloat {
return value * CGFloat(M_PI) / 180.0
}
static func RadiansToDegrees (value:CGFloat) -> CGFloat {
return value * 180.0 / CGFloat(M_PI)
private struct Conversion {
static func degreesToRadians (value:CGFloat) -> CGFloat {
return value * CGFloat.pi / 180.0
}
}
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 +33,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: 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(value: clampedValue, minMax: (r0, r1)), green: lerp(value: clampedValue, minMax: (g0, g1)), blue: lerp(value: clampedValue, minMax: (b0, b1)), alpha: lerp(value: 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: value + range, range: range, minMax: minMax)
} else {
return Mod(value - range, range: range, minMax: minMax)
return mod(value: value - range, range: range, minMax: minMax)
}
}
}
private var progressLayer: KDCircularProgressViewLayer! {
private var progressLayer: KDCircularProgressViewLayer {
get {
return layer as! KDCircularProgressViewLayer
}
@@ -62,7 +80,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 +89,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(value: startAngle, range: 360, minMax: (0, 360))
progressLayer.startAngle = startAngle
progressLayer.setNeedsDisplay()
}
}
@@ -91,6 +110,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,11 +124,12 @@ 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(value: glowAmount, minMax: (0, 1))
progressLayer.glowAmount = glowAmount
}
}
@IBInspectable public var glowMode: KDCircularProgressGlowMode = .Forward {
@IBInspectable public var glowMode: KDCircularProgressGlowMode = .forward {
didSet {
progressLayer.glowMode = glowMode
}
@@ -111,19 +137,19 @@ 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(value: 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(value: trackThickness, minMax: (0, 1))
progressLayer.trackThickness = trackThickness/2
}
}
@IBInspectable public var trackColor: UIColor = UIColor.blackColor() {
@IBInspectable public var trackColor: UIColor = .black {
didSet {
progressLayer.trackColor = trackColor
progressLayer.setNeedsDisplay()
@@ -135,7 +161,7 @@ public class KDCircularProgress: UIView {
if let color = progressInsideFillColor {
progressLayer.progressInsideFillColor = color
} else {
progressLayer.progressInsideFillColor = UIColor.clearColor()
progressLayer.progressInsideFillColor = .clear
}
}
}
@@ -146,7 +172,7 @@ public class KDCircularProgress: UIView {
}
set(newValue) {
setColors(newValue)
setColors(colors: newValue)
}
}
@@ -156,12 +182,11 @@ public class KDCircularProgress: UIView {
@objc @IBInspectable private var IBColor2: UIColor?
@objc @IBInspectable private var IBColor3: UIColor?
private var animationCompletionBlock: ((Bool) -> Void)?
override public init(frame: CGRect) {
super.init(frame: frame)
userInteractionEnabled = false
isUserInteractionEnabled = false
setInitialValues()
refreshValues()
checkAndSetIBColors()
@@ -169,13 +194,13 @@ public class KDCircularProgress: UIView {
convenience public init(frame:CGRect, colors: UIColor...) {
self.init(frame: frame)
setColors(colors)
setColors(colors: colors)
}
required public init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
translatesAutoresizingMaskIntoConstraints = false
userInteractionEnabled = false
isUserInteractionEnabled = false
setInitialValues()
refreshValues()
}
@@ -184,7 +209,7 @@ public class KDCircularProgress: UIView {
checkAndSetIBColors()
}
override public class func layerClass() -> AnyClass {
override public class var layerClass: AnyClass {
return KDCircularProgressViewLayer.self
}
@@ -195,17 +220,18 @@ public class KDCircularProgress: UIView {
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())
backgroundColor = .clear
setColors(colors: .white, .cyan)
}
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
@@ -213,14 +239,14 @@ public class KDCircularProgress: UIView {
}
private func checkAndSetIBColors() {
let nonNilColors = [IBColor1, IBColor2, IBColor3].filter { $0 != nil}.map { $0! }
if nonNilColors.count > 0 {
setColors(nonNilColors)
let nonNilColors = [IBColor1, IBColor2, IBColor3].flatMap { $0 }
if !nonNilColors.isEmpty {
setColors(colors: nonNilColors)
}
}
public func setColors(colors: UIColor...) {
setColors(colors)
setColors(colors: colors)
}
private func setColors(colors: [UIColor]) {
@@ -228,17 +254,17 @@ public class KDCircularProgress: UIView {
progressLayer.setNeedsDisplay()
}
public func animateFromAngle(fromAngle: Int, toAngle: Int, duration: NSTimeInterval, relativeDuration: Bool = true, completion: ((Bool) -> Void)?) {
public func animate(fromAngle: Double, toAngle: Double, duration: TimeInterval, relativeDuration: Bool = true, completion: ((Bool) -> Void)?) {
if isAnimating() {
pauseAnimation()
}
let animationDuration: NSTimeInterval
let animationDuration: TimeInterval
if relativeDuration {
animationDuration = duration
} else {
let traveledAngle = UtilityFunctions.Mod(toAngle - fromAngle, range: 360, minMax: (0, 360))
let scaledDuration = (NSTimeInterval(traveledAngle) * duration) / 360
let traveledAngle = Utility.mod(value: toAngle - fromAngle, range: 360, minMax: (0, 360))
let scaledDuration = (TimeInterval(traveledAngle) * duration) / 360
animationDuration = scaledDuration
}
@@ -250,18 +276,19 @@ public class KDCircularProgress: UIView {
angle = toAngle
animationCompletionBlock = completion
progressLayer.addAnimation(animation, forKey: "angle")
progressLayer.add(animation, forKey: "angle")
}
public func animateToAngle(toAngle: Int, duration: NSTimeInterval, relativeDuration: Bool = true, completion: ((Bool) -> Void)?) {
public func animate(toAngle: Double, duration: TimeInterval, relativeDuration: Bool = true, completion: ((Bool) -> Void)?) {
if isAnimating() {
pauseAnimation()
}
animateFromAngle(angle, toAngle: toAngle, duration: duration, relativeDuration: relativeDuration, completion: completion)
animate(fromAngle: angle, toAngle: toAngle, duration: duration, relativeDuration: relativeDuration, completion: completion)
}
public func pauseAnimation() {
guard let presentationLayer = progressLayer.presentationLayer() as? KDCircularProgressViewLayer else { return }
guard let presentationLayer = progressLayer.presentation() else { return }
let currentValue = presentationLayer.angle
progressLayer.removeAllAnimations()
animationCompletionBlock = nil
@@ -269,18 +296,22 @@ public class KDCircularProgress: UIView {
}
public func stopAnimation() {
animationCompletionBlock = nil
progressLayer.removeAllAnimations()
angle = 0
}
public func isAnimating() -> Bool {
return progressLayer.animationForKey("angle") != nil
return progressLayer.animation(forKey: "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
}
}
@@ -290,7 +321,7 @@ public class KDCircularProgress: UIView {
}
}
public override func willMoveToSuperview(newSuperview: UIView?) {
public override func willMove(toSuperview newSuperview: UIView?) {
if newSuperview == nil && isAnimating() {
pauseAnimation()
}
@@ -304,13 +335,13 @@ public class KDCircularProgress: UIView {
}
private class KDCircularProgressViewLayer: CALayer {
@NSManaged var angle: Int
@NSManaged var angle: Double
var radius: CGFloat! {
didSet {
invalidateGradientCache()
}
}
var startAngle: Int!
var startAngle: Double!
var clockwise: Bool! {
didSet {
if clockwise != oldValue {
@@ -319,6 +350,7 @@ public class KDCircularProgress: UIView {
}
}
var roundedCorners: Bool!
var lerpColorMode: Bool!
var gradientRotateSpeed: CGFloat! {
didSet {
invalidateGradientCache()
@@ -329,24 +361,24 @@ public class KDCircularProgress: UIView {
var progressThickness: CGFloat!
var trackThickness: CGFloat!
var trackColor: UIColor!
var progressInsideFillColor: UIColor = UIColor.clearColor()
var progressInsideFillColor = UIColor.clear
var colorsArray: [UIColor]! {
didSet {
invalidateGradientCache()
}
}
private var gradientCache: CGGradientRef?
private var gradientCache: CGGradient?
private var locationsCache: [CGFloat]?
private struct GlowConstants {
private static let sizeToGlowRatio: CGFloat = 0.00015
static func glowAmountForAngle(angle: Int, glowAmount: CGFloat, glowMode: KDCircularProgressGlowMode, size: CGFloat) -> CGFloat {
static func glowAmount(forAngle angle: Double, glowAmount: CGFloat, glowMode: KDCircularProgressGlowMode, size: CGFloat) -> CGFloat {
switch glowMode {
case .Forward:
case .forward:
return CGFloat(angle) * size * sizeToGlowRatio * glowAmount
case .Reverse:
case .reverse:
return CGFloat(360 - angle) * size * sizeToGlowRatio * glowAmount
case .Constant:
case .constant:
return 360 * size * sizeToGlowRatio * glowAmount
default:
return 0
@@ -354,11 +386,11 @@ public class KDCircularProgress: UIView {
}
}
override class func needsDisplayForKey(key: String) -> Bool {
return key == "angle" ? true : super.needsDisplayForKey(key)
override class func needsDisplay(forKey key: String) -> Bool {
return key == "angle" ? true : super.needsDisplay(forKey: key)
}
override init(layer: AnyObject) {
override init(layer: Any) {
super.init(layer: layer)
let progressLayer = layer as! KDCircularProgressViewLayer
radius = progressLayer.radius
@@ -366,6 +398,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
@@ -384,114 +417,140 @@ public class KDCircularProgress: UIView {
super.init(coder: aDecoder)
}
override func drawInContext(ctx: CGContext) {
override func draw(in 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)
ctx.addArc(center: CGPoint(x: width/2.0, y: height/2.0), radius: arcRadius, startAngle: 0, endAngle: CGFloat.pi * 2, clockwise: false)
trackColor.set()
CGContextSetStrokeColorWithColor(ctx, trackColor.CGColor)
CGContextSetFillColorWithColor(ctx, progressInsideFillColor.CGColor)
CGContextSetLineWidth(ctx, trackLineWidth)
CGContextSetLineCap(ctx, CGLineCap.Butt)
CGContextDrawPath(ctx, .FillStroke)
ctx.setStrokeColor(trackColor.cgColor)
ctx.setFillColor(progressInsideFillColor.cgColor)
ctx.setLineWidth(trackLineWidth)
ctx.setLineCap(CGLineCap.butt)
ctx.drawPath(using: .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)
}
CGContextSetLineCap(imageCtx, roundedCorners == true ? .Round : .Butt)
CGContextSetLineWidth(imageCtx, progressLineWidth)
CGContextDrawPath(imageCtx, .Stroke)
let drawMask: CGImageRef = CGBitmapContextCreateImage(UIGraphicsGetCurrentContext())!
let imageCtx = UIGraphicsGetCurrentContext()
let reducedAngle = Utility.mod(value: angle, range: 360, minMax: (0, 360))
let fromAngle = Conversion.degreesToRadians(value: CGFloat(-startAngle))
let toAngle = Conversion.degreesToRadians(value: CGFloat((clockwise == true ? -reducedAngle : reducedAngle) - startAngle))
imageCtx?.addArc(center: CGPoint(x: width/2.0, y: height/2.0), radius: arcRadius, startAngle: fromAngle, endAngle: toAngle, clockwise: clockwise)
let glowValue = GlowConstants.glowAmount(forAngle: reducedAngle, glowAmount: glowAmount, glowMode: glowMode, size: width)
if glowValue > 0 {
imageCtx?.setShadow(offset: CGSize.zero, blur: glowValue, color: UIColor.black.cgColor)
}
let linecap: CGLineCap = roundedCorners == true ? .round : .butt
imageCtx?.setLineCap(linecap)
imageCtx?.setLineWidth(progressLineWidth)
imageCtx?.drawPath(using: .stroke)
let drawMask: CGImage = UIGraphicsGetCurrentContext()!.makeImage()!
UIGraphicsEndImageContext()
CGContextSaveGState(ctx)
CGContextClipToMask(ctx, bounds, drawMask)
ctx.saveGState()
ctx.clip(to: bounds, mask: 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]
return UIColor(red: whiteValue, green: whiteValue, blue: whiteValue, alpha: 1.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 color.cgColor.numberOfComponents == 2 {
if let whiteValue = color.cgColor.components?[0] {
return UIColor(red: whiteValue, green: whiteValue, blue: whiteValue, alpha: 1.0)
} else {
return UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
}
} else {
return c
return color
}
}
for color in rgbColorsArray {
let colorComponents: UnsafePointer<CGFloat> = CGColorGetComponents(color.CGColor)
componentsArray.appendContentsOf([colorComponents[0],colorComponents[1],colorComponents[2],1.0])
let componentsArray = rgbColorsArray.flatMap { color -> [CGFloat] in
guard let components = color.cgColor.components else { return [] }
return [components[0], components[1], components[2], 1.0]
}
drawGradientWithContext(ctx, componentsArray: componentsArray)
drawGradientWith(context: ctx, componentsArray: componentsArray)
} else {
if colorsArray.count == 1 {
fillRectWithContext(ctx, color: colorsArray[0])
var color: UIColor?
if colorsArray.isEmpty {
color = UIColor.white
} 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(value: t, minMax: ((fi - 1) * step, fi * step))
color = Utility.colorLerp(value: colorT, minMax: (colorsArray[i - 1], colorsArray[i]))
break
}
}
}
if let color = color {
fillRectWith(context: ctx, color: color)
}
}
CGContextRestoreGState(ctx)
ctx.restoreGState()
UIGraphicsPopContext()
}
private func fillRectWithContext(ctx: CGContext!, color: UIColor) {
CGContextSetFillColorWithColor(ctx, color.CGColor)
CGContextFillRect(ctx, bounds)
private func fillRectWith(context: CGContext!, color: UIColor) {
context.setFillColor(color.cgColor)
context.fill(bounds)
}
private func drawGradientWithContext(ctx: CGContext!, componentsArray: [CGFloat]) {
private func drawGradientWith(context: CGContext!, componentsArray: [CGFloat]) {
let baseSpace = CGColorSpaceCreateDeviceRGB()
let locations = locationsCache ?? gradientLocationsFromColorCount(componentsArray.count/4, gradientWidth: bounds.size.width)
let locations = locationsCache ?? gradientLocationsFor(colorCount: componentsArray.count/4, gradientWidth: bounds.size.width)
let gradient: CGGradient
if let g = self.gradientCache {
gradient = g
if let cachedGradient = gradientCache {
gradient = cachedGradient
} else {
guard let g = CGGradientCreateWithColorComponents(baseSpace, componentsArray, locations,componentsArray.count / 4) else { return }
self.gradientCache = g
gradient = g
guard let cachedGradient = CGGradient(colorSpace: baseSpace, colorComponents: componentsArray, locations: locations, count: componentsArray.count/4) else {
return
}
gradientCache = cachedGradient
gradient = cachedGradient
}
let halfX = bounds.size.width/2.0
let floatPi = CGFloat(M_PI)
let halfX = bounds.size.width / 2.0
let floatPi = CGFloat.pi
let rotateSpeed = clockwise == true ? gradientRotateSpeed : gradientRotateSpeed * -1
let angleInRadians = ConversionFunctions.DegreesToRadians(rotateSpeed * CGFloat(angle) - 90)
let angleInRadians = Conversion.degreesToRadians(value: 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, .DrawsBeforeStartLocation)
context.drawLinearGradient(gradient, start: startPoint, end: endPoint, options: .drawsBeforeStartLocation)
}
private func gradientLocationsFromColorCount(colorCount: Int, gradientWidth: CGFloat) -> [CGFloat] {
private func gradientLocationsFor(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
@@ -169,14 +169,18 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0700;
LastUpgradeCheck = 0610;
LastUpgradeCheck = 0800;
ORGANIZATIONNAME = "Kaan Dedeoglu";
TargetAttributes = {
BC9E75851A8CE4A500B1DF3D = {
CreatedOnToolsVersion = 6.1.1;
DevelopmentTeam = N7Y3X223PM;
DevelopmentTeamName = "Kaan Dedeoglu";
LastSwiftMigration = 0800;
};
BC9E759A1A8CE4A500B1DF3D = {
CreatedOnToolsVersion = 6.1.1;
LastSwiftMigration = 0800;
TestTargetID = BC9E75851A8CE4A500B1DF3D;
};
};
@@ -289,8 +293,10 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
@@ -333,6 +339,7 @@
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
@@ -352,7 +359,9 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
INFOPLIST_FILE = KDCircularProgressExample/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "com.kaandedeoglu.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 3.0;
};
name = Debug;
};
@@ -362,7 +371,10 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
INFOPLIST_FILE = KDCircularProgressExample/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "com.kaandedeoglu.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
SWIFT_VERSION = 3.0;
};
name = Release;
};
@@ -380,7 +392,9 @@
);
INFOPLIST_FILE = KDCircularProgressExampleTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "com.kaandedeoglu.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 3.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/KDCircularProgressExample.app/KDCircularProgressExample";
};
name = Debug;
@@ -395,7 +409,10 @@
);
INFOPLIST_FILE = KDCircularProgressExampleTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "com.kaandedeoglu.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
SWIFT_VERSION = 3.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/KDCircularProgressExample.app/KDCircularProgressExample";
};
name = Release;
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0610"
LastUpgradeVersion = "0800"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -37,10 +37,10 @@
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
buildConfiguration = "Debug">
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
@@ -62,15 +62,18 @@
ReferencedContainer = "container:KDCircularProgressExample.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
buildConfiguration = "Debug"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
@@ -86,10 +89,10 @@
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
buildConfiguration = "Release"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
@@ -14,30 +14,30 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
return true
}
func applicationWillResignActive(application: UIApplication) {
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
func applicationDidEnterBackground(application: UIApplication) {
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(application: UIApplication) {
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(application: UIApplication) {
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(application: UIApplication) {
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
@@ -7,7 +7,7 @@
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>com.kaandedeoglu.$(PRODUCT_NAME:rfc1034identifier)</string>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
@@ -9,7 +9,6 @@
import UIKit
class ViewController: UIViewController {
var progress: KDCircularProgress!
override func viewDidLoad() {
@@ -23,19 +22,19 @@ class ViewController: UIViewController {
progress.clockwise = true
progress.gradientRotateSpeed = 2
progress.roundedCorners = false
progress.glowMode = .Forward
progress.glowMode = .forward
progress.glowAmount = 0.9
progress.setColors(UIColor.cyanColor() ,UIColor.whiteColor(), UIColor.magentaColor(), UIColor.whiteColor(), UIColor.orangeColor())
progress.setColors(colors: UIColor.cyan ,UIColor.white, UIColor.magenta, UIColor.white, UIColor.orange)
progress.center = CGPoint(x: view.center.x, y: view.center.y + 25)
view.addSubview(progress)
}
@IBAction func sliderDidChangeValue(sender: UISlider) {
progress.angle = Int(sender.value)
@IBAction func sliderDidChangeValue(_ sender: UISlider) {
progress.angle = Double(sender.value)
}
@IBAction func animateButtonTapped(sender: UIButton) {
progress.animateFromAngle(0, toAngle: 360, duration: 5) { completed in
@IBAction func animateButtonTapped(_ sender: UIButton) {
progress.animate(fromAngle: 0, toAngle: 360, duration: 5) { completed in
if completed {
print("animation stopped, completed")
} else {
@@ -7,7 +7,7 @@
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>com.kaandedeoglu.$(PRODUCT_NAME:rfc1034identifier)</string>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
@@ -28,7 +28,7 @@ class KDCircularProgressExampleTests: XCTestCase {
func testPerformanceExample() {
// This is an example of a performance test case.
self.measureBlock() {
self.measure() {
// Put the code you want to measure the time of here.
}
}
+31 -3
View File
@@ -1,8 +1,18 @@
# 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.
[![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` master branch is now compatible with Swift 3. Check Swift 2 & Swift 2.3 branches for older versions.
`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)
@@ -34,9 +44,27 @@ 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]`
@@ -132,7 +160,7 @@ Prefering light colors in the gradients gives better results. As mentioned befor
##To-Do
- [x] Add example project
- [ ] Carthage Support
- [x] Cocoapods Support
- [x] CocoaPods Support
- [x] IBDesignable/IBInspectable support
- [ ] Adding a `progress` property as an alternative to `angle`
- [ ] Clean up