Last active
September 30, 2018 23:36
-
-
Save theoriginalbit/3b7df489bda64055ce4e5c5231e59f87 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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