Skip to content

Instantly share code, notes, and snippets.

@KalpeshTalkar
Last active August 19, 2020 18:46
Show Gist options
  • Save KalpeshTalkar/0f427d0fb13fa898cadc9cebe4f712c2 to your computer and use it in GitHub Desktop.
Save KalpeshTalkar/0f427d0fb13fa898cadc9cebe4f712c2 to your computer and use it in GitHub Desktop.
Circular progress view build in swift 3
//
// Copyright © 2017 Kalpesh Talkar. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// For support: https://gist.github.com/KalpeshTalkar/0f427d0fb13fa898cadc9cebe4f712c2
//
import UIKit
typealias AnimationCompleted = (_ completed: Bool) -> (Void)
@IBDesignable
class KCircularProgressView: UIView, CAAnimationDelegate {
// MARK: - IBInspectable properties
@IBInspectable var lineWidth: CGFloat = 3 {
didSet {
placeholderLayer.lineWidth = lineWidth
progressLayer.lineWidth = lineWidth
}
}
@IBInspectable var progress: Float = 0 {
didSet {
layoutSubviews()
updateProgressLabelText()
}
}
@IBInspectable var progressTint: UIColor? {
didSet {
progressLayer.strokeColor = progressTint?.cgColor ?? tintColor.cgColor
}
}
@IBInspectable var placeHolderTint: UIColor? {
didSet {
placeholderLayer.strokeColor = placeHolderTint?.cgColor ?? tintColor.cgColor
}
}
@IBInspectable var showsLabel: Bool = true {
didSet {
configureLabel()
}
}
@IBInspectable var progressLabelTint: UIColor? {
didSet {
label.textColor = progressLabelTint ?? tintColor
}
}
// MARK: - Private
private let placeholderLayer = CAShapeLayer()
private let progressLayer = CAShapeLayer()
private var label = UILabel()
// Animation
private var animationBlock: AnimationCompleted?
private let DrawCircleAnimation = "drawCircleAnimation"
// MARK: - init methods
override init(frame: CGRect) {
super.init(frame: frame)
configure()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
}
// MARK: - layout subviews
override func layoutSubviews() {
super.layoutSubviews()
backgroundColor = UIColor.clear
placeholderLayer.frame = bounds
placeholderLayer.path = circlePath(progress: 1).cgPath
progressLayer.frame = bounds
progressLayer.path = circlePath(progress: progress).cgPath
updateLabelFrame()
}
// MARK: - Configure view
private func configure() {
// Background color
backgroundColor = UIColor.clear
// Placeholder layer
placeholderLayer.frame = bounds
placeholderLayer.lineWidth = lineWidth
placeholderLayer.fillColor = UIColor.clear.cgColor
placeholderLayer.strokeColor = placeHolderTint?.cgColor ?? UIColor.groupTableViewBackground.cgColor
layer.addSublayer(placeholderLayer)
// Progress layer
progressLayer.frame = bounds
progressLayer.lineWidth = lineWidth
progressLayer.fillColor = UIColor.clear.cgColor
progressLayer.strokeColor = progressTint?.cgColor ?? tintColor.cgColor
layer.addSublayer(progressLayer)
// Progress label
configureLabel()
}
// MARK: - Configure label
private func configureLabel() {
label.removeFromSuperview()
label = UILabel()
label.frame = bounds
updateLabelFrame()
addSubview(label)
label.textColor = progressLabelTint ?? tintColor
label.textAlignment = .center
updateProgressLabelText()
}
private func updateLabelFrame() {
label.frame = bounds
label.font = UIFont.systemFont(ofSize: bounds.width/3.5)
}
private func updateProgressLabelText() {
label.text = showsLabel ? String(format: "%.0f%@", progress*100,"%") : ""
}
// MARK: - UIBezierPath
private func circlePath(progress: Float) -> UIBezierPath {
let size = bounds.width - (lineWidth*2)
let origin = size/2 + lineWidth
let start_angle = CGFloat(2*Float.pi*0-Float.pi/2)
let end_angle = CGFloat(2*Float.pi*progress-Float.pi/2)
return UIBezierPath(arcCenter: CGPoint(x: origin, y: origin), radius: size/2, startAngle: start_angle, endAngle: end_angle, clockwise: true)
}
// MARK: - Set progress
func setProgress(progress: Float, animated: Bool, animationDuration: CFTimeInterval, completion: AnimationCompleted?) {
// Asign the animation completion block
animationBlock = completion
if animated {
// Configure animation
let drawAnimation = CABasicAnimation(keyPath: "strokeEnd")
drawAnimation.duration = animationDuration // Animation duration
drawAnimation.repeatCount = 0.0 // Animate only once..
drawAnimation.isRemovedOnCompletion = false // Remain stroked after the animation..
// Animate from no part of the stroke being drawn to the entire stroke being drawn
drawAnimation.fromValue = 0.0
drawAnimation.toValue = 1.0
// Experiment with timing to get the appearence to look the way you want
drawAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
drawAnimation.delegate = self
// Add the animation to the circle
progressLayer.add(drawAnimation, forKey: DrawCircleAnimation)
// Set progress
self.progress = progress
} else {
self.progress = progress
}
}
func setProgress(progress: Float, animated: Bool, completion: AnimationCompleted?) {
setProgress(progress: progress, animated: animated, animationDuration: 0.5, completion: completion)
}
// MARK: - CAAnimationDelegate
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
// Remove animation
progressLayer.removeAnimation(forKey: DrawCircleAnimation)
// Call the animation completion block
if nil != animationBlock {
animationBlock!(true)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment