Skip to content

Instantly share code, notes, and snippets.

@katiesmillie
Created February 16, 2017 20:59
Show Gist options
  • Save katiesmillie/c7c096b0e81003a09279c48f9e4c1a5b to your computer and use it in GitHub Desktop.
Save katiesmillie/c7c096b0e81003a09279c48f9e4c1a5b to your computer and use it in GitHub Desktop.
Circular Progress View (starts at the 12:00 position like App Store or Netflix downloading indicator)
// Adapated from https://www.raywenderlich.com/94302/implement-circular-image-loader-animation-cashapelayer
// Swift 3
// Init with frame size or set in storyboard and use an IBOutlet
// Set end stroke to 1 and animate with duration, or pass in the progress (between 0 and 1) while downloading, etc
class CircleProgressView: UIView {
let circlePathLayer = CAShapeLayer()
let circleRadius: CGFloat = 10.0
let progressPathLayer = CAShapeLayer()
let progressRadius: CGFloat = 9.0
let stopButtonPathLayer = CAShapeLayer()
let stopButtonWidth: CGFloat = 5.0
var progress: CGFloat {
get {
return progressPathLayer.strokeEnd
}
set {
if newValue > 1 {
progressPathLayer.strokeEnd = 1
} else if newValue < 0 {
progressPathLayer.strokeEnd = 0
} else {
progressPathLayer.strokeEnd = newValue
}
}
}
override init(frame: CGRect) {
super.init(frame: frame)
configure()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
configure()
}
func configure() {
circlePathLayer.frame = bounds
circlePathLayer.lineWidth = 1
circlePathLayer.fillColor = UIColor.clear.cgColor
circlePathLayer.strokeColor = UIColor.blue.cgColor
circlePathLayer.strokeEnd = 1
progressPathLayer.frame = bounds
progressPathLayer.lineWidth = 2
progressPathLayer.fillColor = UIColor.clear.cgColor
progressPathLayer.strokeColor = UIColor.blue.cgColor
stopButtonPathLayer.fillColor = UIColor.blue.cgColor
stopButtonPathLayer.frame = bounds
layer.addSublayer(circlePathLayer)
layer.addSublayer(progressPathLayer)
layer.addSublayer(stopButtonPathLayer)
backgroundColor = UIColor.white
progress = 0
}
func circleFrame() -> CGRect {
var circleFrame = CGRect(x: 0, y: 0, width: 2 * circleRadius, height: 2 * circleRadius)
circleFrame.origin.x = circlePathLayer.bounds.midX - circleFrame.midX
circleFrame.origin.y = circlePathLayer.bounds.midY - circleFrame.midY
return circleFrame
}
func progressFrame() -> CGRect {
var progressFrame = CGRect(x: 0, y: 0, width: 2 * progressRadius, height: 2 * progressRadius)
progressFrame.origin.x = progressPathLayer.bounds.midX - progressFrame.midX
progressFrame.origin.y = progressPathLayer.bounds.midY - progressFrame.midY
return progressFrame
}
func stopButtonFrame() -> CGRect {
var stopButtonFrame = CGRect(x: 0, y: 0, width: stopButtonWidth, height: stopButtonWidth)
stopButtonFrame.origin.x = stopButtonPathLayer.bounds.midX - stopButtonFrame.midX
stopButtonFrame.origin.y = stopButtonPathLayer.bounds.midY - stopButtonFrame.midY
return stopButtonFrame
}
func circlePath() -> UIBezierPath {
return UIBezierPath(ovalIn: circleFrame())
}
func progressPath() -> UIBezierPath {
return UIBezierPath(ovalIn: progressFrame())
}
func stopButtonPath() -> UIBezierPath {
return UIBezierPath(rect: stopButtonFrame())
}
override func layoutSubviews() {
super.layoutSubviews()
progressPathLayer.frame = bounds
circlePathLayer.frame = bounds
stopButtonPathLayer.frame = bounds
stopButtonPathLayer.path = stopButtonPath().cgPath
circlePathLayer.path = circlePath().cgPath
progressPathLayer.path = progressPath().cgPath
// Rotate the layer so the stroke starts at the 12:00 position
let startAngle = 3 * .pi / 2
progressPathLayer.transform = CATransform3DMakeRotation(startAngle, 0.0, 0.0, 1.0)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment