patternswiftMinor
Draw an arc based on a given percentage
Viewed 0 times
arcdrawbasedgivenpercentage
Problem
I wanted to create a class that would allow me to draw an arc representing a percentage of progress, similar to the activity view on the Apple watch. There are various examples which I found helpful on the web and my output is a combination of many of these.
I have posted my code so that it works and renders in a Xcode playground. Could someone have a look over it and advise if i'm doing anything I shouldn't, or going wrong anywhere.
```
import UIKit
import XCPlayground
class CircleProgressView: UIView {
var startPoint: CGFloat = 0
var color: UIColor = UIColor.yellowColor()
var trackColor: UIColor = UIColor.grayColor()
var trackWidth: CGFloat = 1
var fillPercentage: CGFloat = 100
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.clearColor()
} // init
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.backgroundColor = UIColor.clearColor()
} // init
private func getGraphStartAndEndPointsInRadians() -> (graphStartingPoint: CGFloat, graphEndingPoint: CGFloat) {
// make sure our starting point is at least 0 and less than 100
if ( 0 > self.startPoint ) {
self.startPoint = 0
} else if ( 100 self.fillPercentage ) {
self.fillPercentage = 0
} else if ( 100 self.trackWidth) {
self.trackWidth = 1
} // if
// and our track width cannot be greater than the radius of our circle
if ( radius < self.trackWidth ) {
self.trackWidth = radius
} // if
// we need our graph starting and ending points
let (graphStartingPoint, graphEndingPoint) = self.getGraphStartAndEndPointsInRadians()
// now we need to first draw the track...
var trackPath = UIBezierPath(arcCenter: center, radius: radius - (trackWidth / 2), startAngle: graphStartingPoint, endAngle: 2.0 * CGFloat(M_PI), clockwise: true)
trackPa
I have posted my code so that it works and renders in a Xcode playground. Could someone have a look over it and advise if i'm doing anything I shouldn't, or going wrong anywhere.
```
import UIKit
import XCPlayground
class CircleProgressView: UIView {
var startPoint: CGFloat = 0
var color: UIColor = UIColor.yellowColor()
var trackColor: UIColor = UIColor.grayColor()
var trackWidth: CGFloat = 1
var fillPercentage: CGFloat = 100
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.clearColor()
} // init
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.backgroundColor = UIColor.clearColor()
} // init
private func getGraphStartAndEndPointsInRadians() -> (graphStartingPoint: CGFloat, graphEndingPoint: CGFloat) {
// make sure our starting point is at least 0 and less than 100
if ( 0 > self.startPoint ) {
self.startPoint = 0
} else if ( 100 self.fillPercentage ) {
self.fillPercentage = 0
} else if ( 100 self.trackWidth) {
self.trackWidth = 1
} // if
// and our track width cannot be greater than the radius of our circle
if ( radius < self.trackWidth ) {
self.trackWidth = radius
} // if
// we need our graph starting and ending points
let (graphStartingPoint, graphEndingPoint) = self.getGraphStartAndEndPointsInRadians()
// now we need to first draw the track...
var trackPath = UIBezierPath(arcCenter: center, radius: radius - (trackWidth / 2), startAngle: graphStartingPoint, endAngle: 2.0 * CGFloat(M_PI), clockwise: true)
trackPa
Solution
I want to focus on just the
First of all, from the method name alone, I can tell it does too much. The word "and" should very rarely be used in method names. It's usage is usually a sign that a method does too much. And this one does.
First thing we can do is refactor out the percent-to-radians conversions into their own method.
Next, this logic has no business in the method:
Instead, this logic should go somewhere more closely related to the actual setting of these variables. Like in the
A method with
As an aside, all of your variables should have
So, we're agreed that we'll move side-effecting logic out of this method, and that we'll refactor the percent-to-radian logic out, now we just need to split the methods. We need one for the start point and one for the end point. Why don't we do these as computer properties?
getGraphStartAndEndPointsInRadians() method of your class, as this is the area I see the most problems with a cursory glance.First of all, from the method name alone, I can tell it does too much. The word "and" should very rarely be used in method names. It's usage is usually a sign that a method does too much. And this one does.
First thing we can do is refactor out the percent-to-radians conversions into their own method.
func percentToRadians(percent: CGFloat) -> CGFloat {
return (2 * CGFloat(M_PI)) / 100) * percent
}Next, this logic has no business in the method:
// make sure our starting point is at least 0 and less than 100
if ( 0 > self.startPoint ) {
self.startPoint = 0
} else if ( 100 self.fillPercentage ) {
self.fillPercentage = 0
} else if ( 100 < self.fillPercentage ) {
self.fillPercentage = 100
} // ifInstead, this logic should go somewhere more closely related to the actual setting of these variables. Like in the
didSet method.A method with
get in the name definitely shouldn't have side effects like this. (And we really don't use get in method names either.)As an aside, all of your variables should have
didSet methods that assure that the view is redrawn when their values are changed.So, we're agreed that we'll move side-effecting logic out of this method, and that we'll refactor the percent-to-radian logic out, now we just need to split the methods. We need one for the start point and one for the end point. Why don't we do these as computer properties?
private var startingDrawPoint: CGFloat {
return percentToRadians(CGFloat(self.startPoint) - 25)
}
private var endingDrawPoint: CGFloat {
return percentToRadians(CGFloat(self.fillPercentage - self.startPoint) - 25)
}Code Snippets
func percentToRadians(percent: CGFloat) -> CGFloat {
return (2 * CGFloat(M_PI)) / 100) * percent
}// make sure our starting point is at least 0 and less than 100
if ( 0 > self.startPoint ) {
self.startPoint = 0
} else if ( 100 < self.startPoint ) {
self.startPoint = 100
} // if
// make sure our fill percentage is at least 0 and less than 100
if ( 0 > self.fillPercentage ) {
self.fillPercentage = 0
} else if ( 100 < self.fillPercentage ) {
self.fillPercentage = 100
} // ifprivate var startingDrawPoint: CGFloat {
return percentToRadians(CGFloat(self.startPoint) - 25)
}
private var endingDrawPoint: CGFloat {
return percentToRadians(CGFloat(self.fillPercentage - self.startPoint) - 25)
}Context
StackExchange Code Review Q#97615, answer score: 4
Revisions (0)
No revisions yet.