I change the value of a variable with this function:
func scoreDisplay(score:Double) {
print(score)
CounterView().counter = Int(score)
print(CounterView().counter)
}
This changes the value of counter in my CounterView class. That class looks like this:
import UIKit
#IBDesignable class CounterView: UIView {
let possiblePoints = 100
let π:CGFloat = CGFloat(M_PI)
var counter: Int = 20
#IBInspectable var outlineColor: UIColor = UIColor.blueColor()
#IBInspectable var counterColor: UIColor = UIColor.orangeColor()
override func drawRect(rect: CGRect) {
// 1
let center = CGPoint(x:bounds.width/2, y: bounds.height/2)
// 2
let radius: CGFloat = max(bounds.width, bounds.height)
// 3
let arcWidth: CGFloat = 76
// 4
let startAngle: CGFloat = 3 * π / 4
let endAngle: CGFloat = π / 4
// 5
var path = UIBezierPath(arcCenter: center,
radius: radius/2 - arcWidth/2,
startAngle: startAngle,
endAngle: endAngle,
clockwise: true)
// 6
path.lineWidth = arcWidth
counterColor.setStroke()
path.stroke()
//Draw the outline
//1 - first calculate the difference between the two angles
//ensuring it is positive
let angleDifference: CGFloat = 2 * π - startAngle + endAngle
//then calculate the arc for each single glass
let arcLengthPerPoint = angleDifference / CGFloat(possiblePoints)
//then multiply out by the actual glasses drunk
let outlineEndAngle = arcLengthPerPoint * CGFloat(counter) + startAngle
//2 - draw the outer arc
var outlinePath = UIBezierPath(arcCenter: center,
radius: bounds.width/2 - 2.5,
startAngle: startAngle,
endAngle: outlineEndAngle,
clockwise: true)
//3 - draw the inner arc
outlinePath.addArcWithCenter(center,
radius: bounds.width/2 - arcWidth + 2.5,
startAngle: outlineEndAngle,
endAngle: startAngle,
clockwise: false)
//4 - close the path
outlinePath.closePath()
outlineColor.setStroke()
outlinePath.lineWidth = 5.0
outlinePath.stroke()
}
}
The score is being passed in properly, and prints the correct value - the problem is that as soon as I change the value of CounterView().counter I try to print it out, but it returns as 20 even though I just set the value to a different number.
It's pretty simple.
You create a new instance of CounterView every time you write
CounterView()
So, with your code
CounterView().counter = Int(score)
print(CounterView().counter)
you have created 2 instances of CounterView. The print function is called on the newly created CounterView so its counter value is the default value you set in the implementation: 20. You need to store the instance in a local variable. For example, your method could look like this
func scoreDisplay(score:Double) {
print(score)
let counterView = CounterView()
counterView.counter = Int(score)
print(counterView.counter)
}
This expression
CounterView().counter
creates a new instance of CounterView each time it's called.
You should create one instance, save it in a variable and access counter from that instance.
Related
Imagine I am having a full semi-circle from 0 to Pi from the unit circle. There is a small number on the left side named min and a big number on the right side called max. There are both interchangeable inside the app depending on some factors.
Does anybody of you have a nice idea on how to draw a scale like I did in the drawing below? I would like to have longer lines for every x mod 10 = 0 and three larger ones in between. The grey circle is just for orientation.
So I started with the following piece of code:
let radius = CGFloat(40)
let dashLong = CGFloat(10)
let dashShort = CGFloat(5)
let middle = CGPoint(x: 50, y: 50)
let leftAngle = CGFloat(Double.pi)
let rightAngle = CGFloat(0)
let min = 45 //random num
let max = 117 //random num
let innerPath = UIBezierPath(arcCenter: middle, radius: radius, startAngle: rightAngle, endAngle: leftAngle, clockwise: true)
let middlePath = UIBezierPath(arcCenter: middle, radius: radius+dashShort, startAngle: rightAngle, endAngle: leftAngle, clockwise: true)
let outerPath = UIBezierPath(arcCenter: middle, radius: radius+dashLong, startAngle: rightAngle, endAngle: leftAngle, clockwise: true)
So there is a radius and also the length of the two types of dashes in the scale. I chose 45 and 117 as random integers for the extrem values of the scale. My three paths which do not need to be drawn are just an orientation on where the dashes need to be started and ended on. So for 50,60,...110 there start at the innerPath and go to the outer one, I am pretty sure that must be in the same angle for a dash on all circles.
Does anyone has a very smart idea how to continue this to calc the dashes and draw them without getting messed up code?
Here's the math for drawing a tick mark.
Let's do everything as CGFloat to keep the conversions to a minimum:
let radius: CGFloat = 40
let dashLong: CGFloat = 10
let dashShort: CGFloat 5
let middle = CGPoint(x: 50, y: 50)
let leftAngle: CGFloat = .pi
let rightAngle: CGFloat = 0
let min: CGFloat = 45 //random num
let max: CGFloat = 117 //random num
First, compute your angle.
let value: CGFloat = 50
let angle = (max - value)/(max - min) * .pi
Now compute your two points:
let p1 = CGPoint(x: middle.x + cos(angle) * radius,
y: middle.y - sin(angle) * radius)
// use dashLong for a long tick, and dashShort for a short tick
let radius2 = radius + dashLong
let p2 = CGPoint(x: middle.x + cos(angle) * radius2,
y: middle.y - sin(angle) * radius2)
Then draw a line between p1 and p2.
Note: In iOS, the coordinate system is upside down with +Y being down, which is why the sin calculation is subtracted from middle.y.
Complete Example
enum TickStyle {
case short
case long
}
class ScaleView: UIView {
// ScaleView properties. If any are changed, redraw the view
var radius: CGFloat = 40 { didSet { self.setNeedsDisplay() } }
var dashLong: CGFloat = 10 { didSet { self.setNeedsDisplay() } }
var dashShort: CGFloat = 5 { didSet { self.setNeedsDisplay() } }
var middle = CGPoint(x: 50, y: 50) { didSet { self.setNeedsDisplay() } }
var leftAngle: CGFloat = .pi { didSet { self.setNeedsDisplay() } }
var rightAngle: CGFloat = 0 { didSet { self.setNeedsDisplay() } }
var min: CGFloat = 45 { didSet { self.setNeedsDisplay() } }
var max: CGFloat = 117 { didSet { self.setNeedsDisplay() } }
override func draw(_ rect: CGRect) {
let path = UIBezierPath()
// draw the arc
path.move(to: CGPoint(x: middle.x - radius, y: middle.y))
path.addArc(withCenter: middle, radius: radius, startAngle: leftAngle, endAngle: rightAngle, clockwise: true)
let startTick = ceil(min / 2.5) * 2.5
let endTick = floor(max / 2.5) * 2.5
// add tick marks every 2.5 units
for value in stride(from: startTick, through: endTick, by: 2.5) {
let style: TickStyle = value.truncatingRemainder(dividingBy: 10) == 0 ? .long : .short
addTick(at: value, style: style, to: path)
}
// stroke the path
UIColor.black.setStroke()
path.stroke()
}
// add a tick mark at value with style to path
func addTick(at value: CGFloat, style: TickStyle, to path: UIBezierPath) {
let angle = (max - value)/(max - min) * .pi
let p1 = CGPoint(x: middle.x + cos(angle) * radius,
y: middle.y - sin(angle) * radius)
var radius2 = radius
if style == .short {
radius2 += dashShort
} else if style == .long {
radius2 += dashLong
}
let p2 = CGPoint(x: middle.x + cos(angle) * radius2,
y: middle.y - sin(angle) * radius2)
path.move(to: p1)
path.addLine(to: p2)
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let view = ScaleView(frame: CGRect(x: 50, y: 50, width: 100, height: 60))
view.backgroundColor = .yellow
self.view.addSubview(view)
}
}
Picture of scale running in app:
My suggestion is Draw this semi circle in CALayer and Draw lines from centre of the semi circle in different CALayer and Mask both of them so that It appears Like this
I would like to place UICollectionViewCells along the circular arc (UIBezierPath). Please suggest a formula to calculate center of these cells.
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
}
All these cells are of same constant size, say (100, 100) and x distance (horizontal) between two cells is also constant, say 20.
let startAngle: Double = 0
let endAngle: Double = 180
var arcRadius: CGFloat = 414 // Actually updated dynamically when layout changes
var arcCenter: CGPoint {
get {
return CGPoint(x: contentView.bounds.width/2, y: contentView.bounds.height/2 - arcRadius)
}
}
func drawArc() {
contentView.layer.sublayers?.forEach {
if $0 is CAShapeLayer {
$0.removeFromSuperlayer()
}
}
// Create an arc
let path = UIBezierPath(arcCenter: arcCenter,
radius: arcRadius,
startAngle: degreesToRadians(value: startAngle),
endAngle: degreesToRadians(value: endAngle),
clockwise: true)
// Create a shape layer and add path
let shapeLayer = createShapeLayer()
shapeLayer.path = path.cgPath
contentView.layer.addSublayer(shapeLayer)
}
It can be achieved using parametric equation of circle. Here is my code -
Create a Circle object and then call its func point(startAngle: forItemAtIndex:) to find the center point for these cells.
struct Circle {
var center: CGPoint
var radius: CGFloat
func point(startAngle: Double, forItemAtIndex index: Int) -> CGPoint {
/*
This is based on parametric equation of a circle with center (cx, cy) -
x = cx + radius*cos(angle)
y = cy + radius*sin(angle)
*/
var deltaX: CGFloat = 122 // Fixed horizontal (x) distance between 2 cells' centers
var deltaY = radius - sqrt(pow(radius, 2) - pow(deltaX, 2)) // formula to calculate vertical distance between cells' centers
var actualY = center.y + radius - deltaY
var deltaAngleRadians = asin((actualY - center.y)/radius)
var deltaAngle = radiansToDegrees(value: deltaAngleRadians)
var stepAngle: Double = deltaAngle - startAngle
var actualAngle = startAngle + (Double(index)*stepAngle)
let viewCenterX = radius * cos(degreesToRadians(value: actualAngle)) + center.x
let viewCenterY = radius * sin(degreesToRadians(value: actualAngle)) + center.y
var position = CGPoint(x: viewCenterX, y: viewCenterY)
return position
}
func bezierPath(startAngle: Double = 0, endAngle: Double = 180, clockwise: Bool = true) -> UIBezierPath {
return UIBezierPath(arcCenter: center, radius: radius, startAngle: degreesToRadians(value: startAngle), endAngle: degreesToRadians(value: endAngle), clockwise: true)
}
func degreesToRadians(value: Double) -> CGFloat {
return CGFloat(value * .pi / 180.0)
}
func radiansToDegrees(value: CGFloat) -> Double {
return Double(value * 180.0 / .pi)
}
}
all!
I have created a circular progress view using CoreGraphics that looks and updates like so:
50%
75%
The class is a UIView class, and it has a variable called 'progress' that determines how much of the circle is filled in.
It works well, but I want to be able to animate changes to the progress variable so that the bar animates smoothly.
I have read from myriad examples that I need to have a CALayer class along with the View class, which I have made, however, it doesn't animate at all.
Two questions:
Can I keep the graphic I drew in CoreGraphics, or do I need to somehow redraw it in CALayer?
My current (attempted) solution crashes towards the bottom at: anim.fromValue = pres.progress. What's up?
class CircleProgressView: UIView {
#IBInspectable var backFillColor: UIColor = UIColor.blueColor()
#IBInspectable var fillColor: UIColor = UIColor.greenColor()
#IBInspectable var strokeColor: UIColor = UIColor.greenColor()
dynamic var progress: CGFloat = 0.00 {
didSet {
self.layer.setValue(progress, forKey: "progress")
}
}
var distToDestination: CGFloat = 10.0
#IBInspectable var arcWidth: CGFloat = 20
#IBInspectable var outlineWidth: CGFloat = 5
override class func layerClass() -> AnyClass {
return CircleProgressLayer.self
}
override func drawRect(rect: CGRect) {
var fillColor = self.fillColor
if distToDestination < 3.0 {
fillColor = UIColor.greenColor()
} else {
fillColor = self.fillColor
}
//Drawing the inside of the container
//Drawing in the container
let center = CGPoint(x:bounds.width/2, y: bounds.height/2)
let radius: CGFloat = max(bounds.width, bounds.height) - 10
let startAngle: CGFloat = 3 * π / 2
let endAngle: CGFloat = 3 * π / 2 + 2 * π
let path = UIBezierPath(arcCenter: center, radius: radius/2 - arcWidth/2, startAngle: startAngle, endAngle: endAngle, clockwise: true)
path.lineWidth = arcWidth
backFillColor.setStroke()
path.stroke()
let fill = UIColor.blueColor().colorWithAlphaComponent(0.15)
fill.setFill()
path.fill()
//Drawing the fill path. Same process
let fillAngleLength = (π) * progress
let fillStartAngle = 3 * π / 2 - fillAngleLength
let fillEndAngle = 3 * π / 2 + fillAngleLength
let fillPath_fill = UIBezierPath(arcCenter: center, radius: radius/2 - arcWidth/2, startAngle: fillStartAngle, endAngle: fillEndAngle, clockwise: true)
fillPath_fill.lineWidth = arcWidth
fillColor.setStroke()
fillPath_fill.stroke()
//Drawing container outline on top
let outlinePath_outer = UIBezierPath(arcCenter: center, radius: radius / 2 - outlineWidth / 2, startAngle: startAngle, endAngle: endAngle, clockwise: true)
let outlinePath_inner = UIBezierPath(arcCenter: center, radius: radius / 2 - arcWidth + outlineWidth / 2, startAngle: startAngle, endAngle: endAngle, clockwise: true)
outlinePath_outer.lineWidth = outlineWidth
outlinePath_inner.lineWidth = outlineWidth
strokeColor.setStroke()
outlinePath_outer.stroke()
outlinePath_inner.stroke()
}
}
class CircleProgressLayer: CALayer {
#NSManaged var progress: CGFloat
override class func needsDisplayForKey(key: String) -> Bool {
if key == "progress" {
return true
}
return super.needsDisplayForKey(key)
}
override func actionForKey(key: String) -> CAAction? {
if (key == "progress") {
if let pres = self.presentationLayer() {
let anim: CABasicAnimation = CABasicAnimation.init(keyPath: key)
anim.fromValue = pres.progress
anim.duration = 0.2
return anim
}
return super.actionForKey(key)
} else {
return super.actionForKey(key)
}
}
}
Thanks for the help!
Try this out :)
class ViewController: UIViewController {
let progressView = CircleProgressView(frame:CGRect(x: 0.0, y: 0.0, width: 200.0, height: 200))
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton()
button.frame = CGRectMake(0, 300, 200, 100)
button.backgroundColor = UIColor.yellowColor()
button.addTarget(self, action: #selector(ViewController.tap), forControlEvents: UIControlEvents.TouchUpInside)
view.addSubview(button)
view.addSubview(progressView)
progressView.progress = 1.0
}
func tap() {
if progressView.progress == 0.5 {
progressView.progress = 1.0
} else {
progressView.progress = 0.5
}
}
}
class CircleProgressView: UIView {
dynamic var progress: CGFloat = 0.00 {
didSet {
let animation = CABasicAnimation()
animation.keyPath = "progress"
animation.fromValue = circleLayer().progress
animation.toValue = progress
animation.duration = Double(0.5)
self.layer.addAnimation(animation, forKey: "progress")
circleLayer().progress = progress
}
}
func circleLayer() -> CircleProgressLayer {
return self.layer as! CircleProgressLayer
}
override class func layerClass() -> AnyClass {
return CircleProgressLayer.self
}
}
class CircleProgressLayer: CALayer {
#NSManaged var progress: CGFloat
override class func needsDisplayForKey(key: String) -> Bool {
if key == "progress" {
return true
}
return super.needsDisplayForKey(key)
}
var backFillColor: UIColor = UIColor.blueColor()
var fillColor: UIColor = UIColor.greenColor()
var strokeColor: UIColor = UIColor.greenColor()
var distToDestination: CGFloat = 10.0
var arcWidth: CGFloat = 20
var outlineWidth: CGFloat = 5
override func drawInContext(ctx: CGContext) {
super.drawInContext(ctx)
UIGraphicsPushContext(ctx)
//Drawing in the container
let center = CGPoint(x:bounds.width/2, y: bounds.height/2)
let radius: CGFloat = max(bounds.width, bounds.height) - 10
let startAngle: CGFloat = 3 * CGFloat(M_PI) / 2
let endAngle: CGFloat = 3 * CGFloat(M_PI) / 2 + 2 * CGFloat(M_PI)
let path = UIBezierPath(arcCenter: center, radius: radius/2 - arcWidth/2, startAngle: startAngle, endAngle: endAngle, clockwise: true)
path.lineWidth = arcWidth
backFillColor.setStroke()
path.stroke()
let fill = UIColor.blueColor().colorWithAlphaComponent(0.15)
fill.setFill()
path.fill()
//Drawing the fill path. Same process
let fillAngleLength = (CGFloat(M_PI)) * progress
let fillStartAngle = 3 * CGFloat(M_PI) / 2 - fillAngleLength
let fillEndAngle = 3 * CGFloat(M_PI) / 2 + fillAngleLength
let fillPath_fill = UIBezierPath(arcCenter: center, radius: radius/2 - arcWidth/2, startAngle: fillStartAngle, endAngle: fillEndAngle, clockwise: true)
fillPath_fill.lineWidth = arcWidth
fillColor.setStroke()
fillPath_fill.stroke()
//Drawing container outline on top
let outlinePath_outer = UIBezierPath(arcCenter: center, radius: radius / 2 - outlineWidth / 2, startAngle: startAngle, endAngle: endAngle, clockwise: true)
let outlinePath_inner = UIBezierPath(arcCenter: center, radius: radius / 2 - arcWidth + outlineWidth / 2, startAngle: startAngle, endAngle: endAngle, clockwise: true)
outlinePath_outer.lineWidth = outlineWidth
outlinePath_inner.lineWidth = outlineWidth
strokeColor.setStroke()
outlinePath_outer.stroke()
outlinePath_inner.stroke()
UIGraphicsPopContext()
}
}
Whilst AntonTheDev provides a great answer, his solution does not allow you to animate the CircularProgressView in an animation block, so you cant do neat things like:
UIView.animate(withDuration: 2, delay: 0, options: .curveEaseInOut,
animations: {
circularProgress.progress = 0.76
}, completion: nil)
There's a similar question here with a up to date Swift 3 answer based on ideas from the accepted answer and this post.
This is what the final solution looks like.
Swift 3 Solution
I'm new to swift and to development at all.
I'm trying to create a wheel fortune for my app but I'm having some difficulties. I need to be able to set the number of sections of the wheel each time differently. Any help how to do it?
I tried to create ellipse like that:
import UIKit
class addShape: UIView {
let context = UIGraphicsGetCurrentContext();
override func drawRect(rect: CGRect) {
CGContextSetLineWidth(context, 3.0)
//CGContextSetStrokeColorWithColor(context, UIColor.purpleColor().CGColor)
CGContextStrokeEllipseInRect(context, rect)
UIColor.redColor().set()
//Actually draw the path
CGContextStrokePath(context)
}
}
but I could not create the ellipse at all :(
I tried this way as well:
override func drawRect(rect: CGRect) {
////UTUBE
UIGraphicsBeginImageContextWithOptions(CGSize(width: 512, height: 512), false, 0)
// Define the center point of the view where you’ll rotate the arc around
let center = CGPoint(x:bounds.width/2, y: bounds.height/2)
// Calculate the radius based on the max dimension of the view.
let radius: CGFloat = max(bounds.width, bounds.height)
// Define the thickness of the arc.
let arcWidth: CGFloat = 115
/////FIRST Shape
// Define the start and end angles for the arc.
let startAngle: CGFloat = 2 * π
let endAngle: CGFloat = π / 4
// Create a path based on the center point, radius, and angles you just defined.
var path = UIBezierPath(arcCenter: center,
radius: radius/2 - arcWidth/2,
startAngle: startAngle,
endAngle: endAngle,
clockwise: true)
// Set the line width and color before finally stroking the path.
path.lineWidth = arcWidth
counterColor.setStroke()
path.stroke()
// Second Shape
let startAngle2: CGFloat = π / 4
let endAngle2: CGFloat = π / 2
// Create a path based on the center point, radius, and angles you just defined.
path = UIBezierPath(arcCenter: center,
radius: radius/2 - arcWidth/2,
startAngle: startAngle2,
endAngle: endAngle2,
clockwise: true)
path.lineWidth = arcWidth
UIColor.greenColor().setStroke()
path.stroke()
// 3rd Shape
let startAngle3: CGFloat = π / 2
let endAngle3: CGFloat = 3 * π / 4
// Create a path based on the center point, radius, and angles you just defined.
path = UIBezierPath(arcCenter: center,
radius: radius/2 - arcWidth/2,
startAngle: startAngle3,
endAngle: endAngle3,
clockwise: true)
path.lineWidth = arcWidth
UIColor.blueColor().setStroke()
path.stroke()
// 4th Shape
let startAngle4: CGFloat = 3 * π / 4
let endAngle4: CGFloat = π
// Create a path based on the center point, radius, and angles you just defined.
path = UIBezierPath(arcCenter: center,
radius: radius/2 - arcWidth/2,
startAngle: startAngle4,
endAngle: endAngle4,
clockwise: true)
path.lineWidth = arcWidth
UIColor.redColor().setStroke()
path.stroke()
// 5th Shape
let startAngle5: CGFloat = π
let endAngle5: CGFloat = 5 * π / 4
// Create a path based on the center point, radius, and angles you just defined.
path = UIBezierPath(arcCenter: center,
radius: radius/2 - arcWidth/2,
startAngle: startAngle5,
endAngle: endAngle5,
clockwise: true)
path.lineWidth = arcWidth
UIColor.yellowColor().setStroke()
path.stroke()
// 6th Shape
let startAngle6: CGFloat = 5 * π / 4
let endAngle6: CGFloat = 3 * π / 2
// Create a path based on the center point, radius, and angles you just defined.
path = UIBezierPath(arcCenter: center,
radius: radius/2 - arcWidth/2,
startAngle: startAngle6,
endAngle: endAngle6,
clockwise: true)
path.lineWidth = arcWidth
UIColor.grayColor().setStroke()
path.stroke()
// 7th Shape
let startAngle7: CGFloat = 3 * π / 2
let endAngle7: CGFloat = 7 * π / 4
// Create a path based on the center point, radius, and angles you just defined.
path = UIBezierPath(arcCenter: center,
radius: radius/2 - arcWidth/2,
startAngle: startAngle7,
endAngle: endAngle7,
clockwise: true)
path.lineWidth = arcWidth
UIColor.purpleColor().setStroke()
path.stroke()
// 8th Shape
let startAngle8: CGFloat = 7 * π / 4
let endAngle8: CGFloat = 2 * π
// Create a path based on the center point, radius, and angles you just defined.
path = UIBezierPath(arcCenter: center,
radius: radius/2 - arcWidth/2,
startAngle: startAngle8,
endAngle: endAngle8,
clockwise: true)
path.lineWidth = arcWidth
UIColor.lightGrayColor().setStroke()
path.stroke()
}
I don't think that's the way to do it, but I did not find any other way to do this.
The main problem with your first example is that your context needs to be retrieved as part of drawRect. Once you have a circle, creating n segments is just a question of drawing n-1 lines.
(Insetting for line width increases complication but looks nicer.)
class WheelView: UIView {
var pieSections: Int = 1
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func drawRect(rect: CGRect) {
let context = UIGraphicsGetCurrentContext();
let lineWidth: CGFloat = 3.0
// Draw outline
CGContextSetLineWidth(context, lineWidth)
UIColor.redColor().set()
let smaller = rect.insetBy(dx: lineWidth / 2.0, dy: lineWidth / 2.0)
CGContextStrokeEllipseInRect(context, smaller)
let radius = smaller.width / 2.0
let segmentAngle = 2.0 / CGFloat(pieSections) * CGFloat(M_PI)
// Draw segment dividers
for index in 1...pieSections {
let drawAngle = segmentAngle * CGFloat(index)
let x = radius * cos(drawAngle) + rect.width / 2.0
let y = radius * sin(drawAngle) + rect.height / 2.0
CGContextBeginPath(context)
CGContextMoveToPoint(context, rect.width / 2, rect.height / 2)
CGContextAddLineToPoint(context, x, y)
CGContextStrokePath(context)
}
}
}
I'm trying to add an animation to my UIBezierPath, but I want to keep the hard edges my current fillPath is creating, here's a screenshot of what the result should look like:
I did manage to get one animation working correctly using a slightly modified version of this code:
// create an object that represents how the curve
// should be presented on the screen
let progressLine = CAShapeLayer()
progressLine.path = ovalPath.CGPath
progressLine.strokeColor = UIColor.blueColor().CGColor
progressLine.fillColor = UIColor.clearColor().CGColor
progressLine.lineWidth = 10.0
progressLine.lineCap = kCALineCapRound
// add the curve to the screen
self.view.layer.addSublayer(progressLine)
// create a basic animation that animates the value 'strokeEnd'
// from 0.0 to 1.0 over 3.0 seconds
let animateStrokeEnd = CABasicAnimation(keyPath: "strokeEnd")
animateStrokeEnd.duration = 3.0
animateStrokeEnd.fromValue = 0.0
animateStrokeEnd.toValue = 1.0
// add the animation
progressLine.addAnimation(animateStrokeEnd, forKey: "animate stroke end animation")
The above code is from: http://mathewsanders.com/animations-in-swift-part-two/
It did work, but the stroke it created was rounded.
The only examples/tutorials and other questions I could find on this matter, was using CAShapes. I'm currently not using CAShapes or layers. (I don't know if that's needed to make an animation work with this, but I figure I'd ask)
Here's my current code (without any animation). To draw my little circle chart.
class CircleChart: UIView {
var percentFill: Float = 99.00 {
didSet {
// Double check that it's less or equal to 100 %
if percentFill <= maxPercent {
//the view needs to be refreshed
setNeedsDisplay()
}
}
}
var fillColor: UIColor = UIColor.formulaBlueColor()
var chartColor: UIColor = UIColor.formulaLightGrayColor()
override func drawRect(rect: CGRect) {
// Define the center.
let center = CGPoint(x:bounds.width/2, y: bounds.height/2)
// Define the radius
let radius: CGFloat = max(bounds.width, bounds.height)
// Define the width of the circle
let arcWidth: CGFloat = 10
// Define starting and ending angles (currently unused)
let startAngle: CGFloat = degreesToRadians(-90)
let endAngle: CGFloat = degreesToRadians(270)
// 5
var path = UIBezierPath(arcCenter: center,
radius: bounds.width/2 - arcWidth/2,
startAngle: startAngle,
endAngle: endAngle,
clockwise: true)
// 6
path.lineWidth = arcWidth
chartColor.setStroke()
path.stroke()
//Draw the fill-part
//calculate the arc for each per percent
let arcLengthPerPercent = degreesToRadians(360/100)
//then multiply out by the actual percent
let fillEndAngle = arcLengthPerPercent * CGFloat(percentFill) + startAngle
//2 - draw the outer arc
var fillPath = UIBezierPath(arcCenter: center,
radius: bounds.width/2 - arcWidth/2,
startAngle: startAngle,
endAngle: fillEndAngle,
clockwise: true)
//3 - draw the inner arc
fillPath.addArcWithCenter(center,
radius: bounds.width/2 - arcWidth/2,
startAngle: fillEndAngle,
endAngle: startAngle,
clockwise: false)
//4 - close the path
fillPath.closePath()
fillColor.setStroke()
fillPath.lineWidth = arcWidth
fillPath.stroke()
}
}
What would be the best way to animate the fillPath? Or do I need to redo everything in CAShapes and layers, to make it work?
Any help would be greatly appreciated.
If you want it square, why are you using progressLine.lineCap = kCALineCapRound? Just delete that line, and you'll get the default butt end (kCALineCapButt).