Skip to content

Instantly share code, notes, and snippets.

@theoriginalbit
Last active September 30, 2018 23:36
Show Gist options
  • Save theoriginalbit/3b7df489bda64055ce4e5c5231e59f87 to your computer and use it in GitHub Desktop.
Save theoriginalbit/3b7df489bda64055ce4e5c5231e59f87 to your computer and use it in GitHub Desktop.
//
// CircularProgressBar.swift
//
// Created by Joshua Asbury on 29/9/18.
// Copyright © 2018 Joshua Asbury. All rights reserved.
//
import UIKit
@IBDesignable public class CircularProgressBar: UIView {
// MARK: Properties
@IBInspectable public var lineWidth: CGFloat = 6 {
didSet {
trackLayer.lineWidth = lineWidth
progressLayer.lineWidth = lineWidth
}
}
@IBInspectable public var trackTintColor: UIColor = .lightGray {
didSet {
trackLayer.strokeColor = trackTintColor.cgColor
}
}
@IBInspectable public var progressTintColor: UIColor = .red {
didSet {
progressLayer.strokeColor = progressTintColor.cgColor
}
}
public var animationDuration: Double = 0.4
private let trackLayer = CAShapeLayer()
private let progressLayer = CAShapeLayer()
// MARK: UIView overrides
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
override public func prepareForInterfaceBuilder() {
commonInit()
}
private func commonInit() {
let diameter = min(self.bounds.width, self.bounds.height) - lineWidth
// configure track layer
trackLayer.path = trackPath(withDiameter: diameter).cgPath
trackLayer.lineWidth = lineWidth
trackLayer.fillColor = nil
trackLayer.strokeColor = trackTintColor.cgColor
self.layer.addSublayer(trackLayer)
// configure progress layer
progressLayer.path = progressPath(withDiameter: diameter).cgPath
progressLayer.lineWidth = lineWidth
progressLayer.fillColor = nil
progressLayer.strokeColor = progressTintColor.cgColor
progressLayer.strokeEnd = 0
self.layer.addSublayer(progressLayer)
}
private func trackPath(withDiameter diameter: CGFloat) -> UIBezierPath {
let x = (self.bounds.width - diameter) / 2
let y = (self.bounds.height - diameter) / 2
return UIBezierPath(ovalIn: CGRect(x: x, y: y, width: diameter, height: diameter))
}
private func progressPath(withDiameter diameter: CGFloat) -> UIBezierPath {
let radius = diameter / 2
let x = ((self.bounds.width - diameter) / 2) + radius
let y = ((self.bounds.height - diameter) / 2) + radius
let startAngle = -CGFloat.pi / 2
let endAngle = 2 * CGFloat.pi + startAngle
return UIBezierPath(arcCenter: CGPoint(x: x, y: y), radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true)
}
public func setProgress(_ value: CGFloat, animated: Bool) {
let progress = max(min(value, 1.0), 0.0)
if animated {
let animation = CABasicAnimation(keyPath: #keyPath(CAShapeLayer.strokeEnd))
animation.duration = animationDuration
animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
animation.toValue = progress
// ensure the animation isn't reverted once complete
animation.fillMode = .forwards
animation.isRemovedOnCompletion = false
progressLayer.add(animation, forKey: nil)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment