Animating a circular progress circle smoothly via buttons - ios

I am experiencing odd visual quirks with my progress circle. I would like the green circle (CAShapeLayer) to smoothly animate in increments of fifths. 0/5 = no green circle. 5/5 = full green circle. The problem is the green circle is animating past where it should be, then abruptly shrinks back to the proper spot. This happens when both the plus and minus button are pressed. There is a gif below demonstrating what is happening.
The duration of each animation should last 0.25 seconds, and should smoothly animate both UP and DOWN (depending if the plus or minus button is pressed) from wherever the animation ended last (which is currently not happening.)
In my UIView, I draw the circle, along with the method to animate the position of the progress:
class CircleView: UIView {
let progressCircle = CAShapeLayer()
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
override func draw(_ rect: CGRect) {
let circlePath = UIBezierPath(ovalIn: self.bounds)
progressCircle.path = circlePath.cgPath
progressCircle.strokeColor = UIColor.green.cgColor
progressCircle.fillColor = UIColor.red.cgColor
progressCircle.lineWidth = 10.0
// Add the circle to the view.
self.layer.addSublayer(progressCircle)
}
func animateCircle(circleToValue: CGFloat) {
let fifths:CGFloat = circleToValue / 5
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.duration = 0.25
animation.fromValue = fifths
animation.byValue = fifths
animation.fillMode = kCAFillModeBoth
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
progressCircle.strokeEnd = fifths
// Create the animation.
progressCircle.add(animation, forKey: "strokeEnd")
}
}
In my VC:
I init the circle and starting point with:
let myDrawnCircle = CircleView()
var startingPointForCircle = CGFloat()
viewDidAppear:
startingPointForCircle = 0.0
myDrawnCircle.frame = CGRect(x: 100, y: 100, width: 150, height: 150)
myDrawnCircle.backgroundColor = UIColor.white
myDrawnCircle.animateCircle(circleToValue: startingPointForCircle)
self.view.addSubview(myDrawnCircle)
textLabel.text = "\(startingPointForCircle)"
And the actual buttons that do the cool animating:
#IBAction func minusButtonPressed(_ sender: UIButton) {
if startingPointForCircle > 0 {
startingPointForCircle -= 1
textLabel.text = "\(startingPointForCircle)"
myDrawnCircle.animateCircle(circleToValue: startingPointForCircle)
}
}
#IBAction func plusButtonPressed(_ sender: UIButton) {
if startingPointForCircle < 5 {
startingPointForCircle += 1
textLabel.text = "\(startingPointForCircle)"
myDrawnCircle.animateCircle(circleToValue: startingPointForCircle)
}
}
Here's a gif showing you what's going on with the jerky animations.
How do I make my animations smoothly animate TO the proper value FROM the proper value?

My guess is it has something to do with using a key path. I don't know how exactly to fix it for using a key path, but you could try a different tack, using a custom view, and animating the value passed to the method used for drawing an arc.
The code below was generated by PaintCode. The value passed for arc specifies the point on the circle to draw the arc to. This is used in the drawRect method of a custom UIView. Make a CGFloat property of the custom view and simply change that value, and call setNeedsDisplay on the view after your animation code.
func drawCircleProgress(frame frame: CGRect = CGRect(x: 0, y: 0, width: 40, height: 40), arc: CGFloat = 270) {
//// General Declarations
let context = UIGraphicsGetCurrentContext()
//// Color Declarations
let white = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000)
let white50 = white.colorWithAlpha(0.5)
//// Oval Drawing
CGContextSaveGState(context)
CGContextTranslateCTM(context, frame.minX + 20, frame.minY + 20)
CGContextRotateCTM(context, -90 * CGFloat(M_PI) / 180)
let ovalRect = CGRect(x: -19.5, y: -19.5, width: 39, height: 39)
let ovalPath = UIBezierPath()
ovalPath.addArcWithCenter(CGPoint(x: ovalRect.midX, y: ovalRect.midY), radius: ovalRect.width / 2, startAngle: 0 * CGFloat(M_PI)/180, endAngle: -arc * CGFloat(M_PI)/180, clockwise: true)
white.setStroke()
ovalPath.lineWidth = 1
ovalPath.stroke()
CGContextRestoreGState(context)
}

The desired results were as simple as commenting out fromValue / byValue in the animateCircle method. This was pointed out by #dfri - "set both fromValue and toValue to nil then: "Interpolates between the previous value of keyPath in the target layer’s presentation layer and the current value of keyPath in the target layer’s presentation layer."
//animation.fromValue = fifths
//animation.byValue = fifths

Related

Blink Arrow draw with UIBezierPath

I have an UIScrollView which has a long text in it. I want to inform users that It has more content to read. Therefore, I added an arrow with UIBezierPath on bottom of it.
class ArrowView: UIView {
var arrowPath: UIBezierPath!
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
// Drawing code
self.drawArrow(from: CGPoint(x: rect.midX, y: rect.minY), to: CGPoint(x: rect.midX, y: rect.maxY),
tailWidth: 10, headWidth: 25, headLength: 20)
//arrowPath.fill()
}
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.darkGray
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
private func drawArrow(from start: CGPoint, to end: CGPoint, tailWidth: CGFloat, headWidth: CGFloat, headLength: CGFloat){
let length = hypot(end.x - start.x, end.y - start.y)
let tailLength = length - headLength
func p(_ x: CGFloat, _ y: CGFloat) -> CGPoint { return CGPoint(x: x, y: y) }
let points: [CGPoint] = [
p(0, tailWidth / 2),
p(tailLength, tailWidth / 2),
p(tailLength, headWidth / 2),
p(length, 0),
p(tailLength, -headWidth / 2),
p(tailLength, -tailWidth / 2),
p(0, -tailWidth / 2)
]
let cosine = (end.x - start.x) / length
let sine = (end.y - start.y) / length
let transform = CGAffineTransform(a: cosine, b: sine, c: -sine, d: cosine, tx: start.x, ty: start.y)
let path = CGMutablePath()
path.addLines(between: points, transform: transform)
path.closeSubpath()
arrowPath = UIBezierPath.init(cgPath: path)
}
}
My question: How can I achieve a blink animation on arrow. Assume that It has a gradient layer from white to blue. It should start with white to blue then blue should seen on start point then white should start to seen on finish point of Arrow and this circle should continue.
On final, this animation should inform users that they can scroll the view.
How can I achieve this?
You can try using CAGradientLayer along with CAAnimation. Add gradient to your view:
override func viewDidAppear(animated: Bool) {
self.gradient = CAGradientLayer()
self.gradient?.frame = self.view.bounds
self.gradient?.colors = [ UIColor.white.cgColor, UIColor.white.cgColor]
self.view.layer.insertSublayer(self.gradient, atIndex: 0)
animateLayer()
}
func animateLayer(){
var fromColors = self.gradient.colors
var toColors: [AnyObject] = [UIColor.blue.cgColor,UIColor.blue.cgColor]
self.gradient.colors = toColors
var animation : CABasicAnimation = CABasicAnimation(keyPath: "colors")
animation.fromValue = fromColors
animation.toValue = toColors
animation.duration = 3.00
animation.isRemovedOnCompletion = true
animation.fillMode = CAMediaTimingFillMode.forwards
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
animation.delegate = self
self.gradient?.addAnimation(animation, forKey:"animateGradient")
}
and to continue gradient animation in cyclic order like:
override func animationDidStop(anim: CAAnimation!, finished flag: Bool) {
self.toColors = self.fromColors;
self.fromColors = self.gradient?.colors
animateLayer()
}
You could put your arrow inside a UIView object. You could then have the UIView on a timer where it changed the alpha properly. Depending on blinking or fading, nonetheless, you could use UIView Animations to complete your desired animation.
I say a uiview, instead of using the scrollview because you don’t want to hide the scrollview. So you’d put it inside a view in which is only a container for the bezierpath.
I solved it by using CABasicAnimation(). This first add gradient layer to custom UIView then apply mask on it then add animation.
override func draw(_ rect: CGRect) {
// Drawing code
self.drawArrow(from: CGPoint(x: rect.midX, y: rect.minY), to: CGPoint(x: rect.midX, y: rect.maxY),
tailWidth: 10, headWidth: 20, headLength: 15)
//arrowPath.fill()
let startColor = UIColor(red:0.87, green:0.87, blue:0.87, alpha:1.0).cgColor
let finishColor = UIColor(red:0.54, green:0.54, blue:0.57, alpha:1.0).withAlphaComponent(0.5).cgColor
let gradient = CAGradientLayer()
gradient.frame = self.bounds
gradient.colors = [startColor,finishColor]
let shapeMask = CAShapeLayer()
shapeMask.path = arrowPath.cgPath
gradient.mask = shapeMask
let animation = CABasicAnimation(keyPath: "colors")
animation.fromValue = [startColor, finishColor]
animation.toValue = [finishColor, startColor]
animation.duration = 2.0
animation.autoreverses = true
animation.repeatCount = Float.infinity
//add the animation to the gradient
gradient.add(animation, forKey: nil)
self.layer.addSublayer(gradient)
}

iOS animate drawing of multiple circles, only one displays

Based on the answer to this question: Animate drawing of a circle
I now want to display two of these circles simultaneously on the same screen but in two different views. If I just want to animate one circle there are no problems. However if I try to add a second, only the second one is visible.
This is the Circle class:
import UIKit
var circleLayer: CAShapeLayer!
class Circle: UIView {
init(frame: CGRect, viewLayer: CALayer) {
super.init(frame: frame)
self.backgroundColor = UIColor.clear
// Use UIBezierPath as an easy way to create the CGPath for the layer.
// The path should be the entire circle.
let circlePath = UIBezierPath(arcCenter: CGPoint(x: frame.size.width / 2.0, y: frame.size.height / 2.0), radius: (frame.size.width - 10)/2, startAngle: (3.0 * .pi)/2.0, endAngle: CGFloat((3.0 * .pi)/2.0 + (.pi * 2.0)), clockwise: true)
// Setup the CAShapeLayer with the path, colors, and line width
circleLayer = CAShapeLayer()
circleLayer.path = circlePath.cgPath
circleLayer.fillColor = UIColor.clear.cgColor
circleLayer.strokeColor = UIColor.green.cgColor
circleLayer.lineWidth = 8.0;
// Don't draw the circle initially
circleLayer.strokeEnd = 0.0
// Add the circleLayer to the view's layer's sublayers
viewLayer.addSublayer(circleLayer)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func animateCircle(duration: TimeInterval) {
// We want to animate the strokeEnd property of the circleLayer
let animation = CABasicAnimation(keyPath: "strokeEnd")
// Set the animation duration appropriately
animation.duration = duration
// Animate from 0 (no circle) to 1 (full circle)
animation.fromValue = 0
animation.toValue = 1
// Do a linear animation (i.e The speed of the animation stays the same)
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
// Set the circleLayer's strokeEnd property to 1.0 now so that it's the
// Right value when the animation ends
circleLayer.strokeEnd = 1.0
// Do the actual animation
circleLayer.add(animation, forKey: "animateCircle")
}
func removeCircle() {
circleLayer.strokeEnd = 0.0
}
}
And here is how I call it from my ViewController:
var rythmTimer: Circle?
var adrenalineTimer: Circle?
override func viewDidLoad() {
// Create two timers as circles
self.rythmTimer = Circle(frame: CGRect(x: 0, y: 0, width: 100, height: 100), viewLayer: view1.layer)
if let rt = rythmTimer {
view1.addSubview(rt)
rt.center = CGPoint(x: self.view1.bounds.midX, y: self.view1.bounds.midY);
}
self.adrenalineTimer = Circle(frame: CGRect(x: 0, y: 0, width: 100, height: 100), viewLayer: view2.layer)
if let at = adrenalineTimer {
view2.addSubview(at)
at.center = CGPoint(x: self.view2.bounds.midX, y: self.view2.bounds.midY)
}
}
If I remove the code for the adrenalineTimer I can see the circle drawn by the rythmTimer. If I keep it the rythmTimer will be displayed in view2 instead of view1 and will have the duration/color of the rythmTimer
It seems you set the circle path with wrong origin points. In addition, you do it before you place the circle on the view.
Add this code to the animate function instead:
func animateCircle(duration: TimeInterval) {
let circlePath = UIBezierPath(arcCenter: CGPoint(x: self.frame.origin.x + self.frame.size.width / 2, y: self.frame.origin.y + self.frame.size.height / 2), radius: (frame.size.width - 10)/2, startAngle: (3.0 * .pi)/2.0, endAngle: CGFloat((3.0 * .pi)/2.0 + (.pi * 2.0)), clockwise: true)
circleLayer.path = circlePath.cgPath
// We want to animate the strokeEnd property of the circleLayer
let animation = CABasicAnimation(keyPath: "strokeEnd")
...
The main problem is that you have declared circleLayer outside of the class:
import UIKit
var circleLayer: CAShapeLayer!
class Circle: UIView {
init(frame: CGRect, viewLayer: CALayer) {
super.init(frame: frame)
self.backgroundColor = UIColor.clear
...
}
}
The result is that you only ever have ONE circleLayer object (instance).
If you move it inside the class, then each instance of Circle will have its own instance of circleLayer:
import UIKit
class Circle: UIView {
var circleLayer: CAShapeLayer!
init(frame: CGRect, viewLayer: CALayer) {
super.init(frame: frame)
self.backgroundColor = UIColor.clear
...
}
}
There are a number of other odd things you're doing, but that is why you only get one animated circle.
Edit: Here's a modified version of your code that will allow you to use auto-layout instead of fixed / hard-coded sizes and positions. You can run this directly in a Playground page:
import UIKit
import PlaygroundSupport
class Circle: UIView {
var circleLayer: CAShapeLayer!
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.clear
// Setup the CAShapeLayer with colors and line width
circleLayer = CAShapeLayer()
circleLayer.fillColor = UIColor.clear.cgColor
circleLayer.strokeColor = UIColor.green.cgColor
circleLayer.lineWidth = 8.0;
// We haven't set the path yet, so don't draw initially
circleLayer.strokeEnd = 0.0
// Add the layer to the self's layer
self.layer.addSublayer(circleLayer)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func layoutSubviews() {
super.layoutSubviews()
// Use UIBezierPath as an easy way to create the CGPath for the layer.
// The path should be the entire circle.
// this will update whenever the frame size changes (makes it easy to use with auto-layout)
let circlePath = UIBezierPath(arcCenter: CGPoint(x: frame.size.width / 2.0, y: frame.size.height / 2.0), radius: (frame.size.width - 10)/2, startAngle: (3.0 * .pi)/2.0, endAngle: CGFloat((3.0 * .pi)/2.0 + (.pi * 2.0)), clockwise: true)
circleLayer.path = circlePath.cgPath
}
func animateCircle(duration: TimeInterval) {
// We want to animate the strokeEnd property of the circleLayer
let animation = CABasicAnimation(keyPath: "strokeEnd")
// Set the animation duration appropriately
animation.duration = duration
// Animate from 0 (no circle) to 1 (full circle)
animation.fromValue = 0
animation.toValue = 1
// Do a linear animation (i.e The speed of the animation stays the same)
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
// Set the circleLayer's strokeEnd property to 1.0 now so that it's the
// Right value when the animation ends
circleLayer.strokeEnd = 1.0
// Do the actual animation
circleLayer.add(animation, forKey: "animateCircle")
}
func removeCircle() {
circleLayer.strokeEnd = 0.0
}
}
class MyViewController : UIViewController {
var rythmTimer: Circle?
var adrenalineTimer: Circle?
var theButton: UIButton = {
let b = UIButton()
b.setTitle("Tap Me", for: .normal)
b.translatesAutoresizingMaskIntoConstraints = false
b.backgroundColor = .red
return b
}()
var view1: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .blue
return v
}()
var view2: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .orange
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.white
view.addSubview(theButton)
// constrain button to Top: 32 and centerX
NSLayoutConstraint.activate([
theButton.topAnchor.constraint(equalTo: view.topAnchor, constant: 32.0),
theButton.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0.0),
])
// add an action for the button tap
theButton.addTarget(self, action: #selector(didTap(_:)), for: .touchUpInside)
view.addSubview(view1)
view.addSubview(view2)
NSLayoutConstraint.activate([
view1.widthAnchor.constraint(equalToConstant: 120.0),
view1.heightAnchor.constraint(equalTo: view1.widthAnchor, multiplier: 1.0),
view1.centerXAnchor.constraint(equalTo: view.centerXAnchor),
view1.topAnchor.constraint(equalTo: theButton.bottomAnchor, constant: 40.0),
view2.widthAnchor.constraint(equalTo: view1.widthAnchor, multiplier: 1.0),
view2.heightAnchor.constraint(equalTo: view1.widthAnchor, multiplier: 1.0),
view2.centerXAnchor.constraint(equalTo: view.centerXAnchor),
view2.topAnchor.constraint(equalTo: view1.bottomAnchor, constant: 40.0),
])
rythmTimer = Circle(frame: CGRect.zero)
adrenalineTimer = Circle(frame: CGRect.zero)
if let rt = rythmTimer,
let at = adrenalineTimer {
view1.addSubview(rt)
view2.addSubview(at)
rt.translatesAutoresizingMaskIntoConstraints = false
at.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
rt.widthAnchor.constraint(equalToConstant: 100.0),
rt.heightAnchor.constraint(equalToConstant: 100.0),
rt.centerXAnchor.constraint(equalTo: view1.centerXAnchor),
rt.centerYAnchor.constraint(equalTo: view1.centerYAnchor),
at.widthAnchor.constraint(equalToConstant: 100.0),
at.heightAnchor.constraint(equalToConstant: 100.0),
at.centerXAnchor.constraint(equalTo: view2.centerXAnchor),
at.centerYAnchor.constraint(equalTo: view2.centerYAnchor),
])
}
}
// on button tap, change the text in the label(s)
#objc func didTap(_ sender: Any?) -> Void {
if let rt = rythmTimer,
let at = adrenalineTimer {
rt.removeCircle()
at.removeCircle()
rt.animateCircle(duration: 2.0)
at.animateCircle(duration: 2.0)
}
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()

Auto-starting a strokeEnd Animation

I have an animation for drawing a circle. I combine 3 different circle animations to create a stylised graph/pie chart.
They animate perfection when in an #IBAction from a button. But how would I start the animation automatically when the Storyboard/ViewController is opened/navigated to?
Animation code as follows:
//NF Colour
let colorNF = UIColor(red: 28/255, green: 117/255, blue: 189/255, alpha: 1)
// Create Holding Rectangle and Start and End Angles
let ovalStartAngleNF = CGFloat(-90.01 * .pi/180)
let ovalEndAngleNF = CGFloat(10.0 * .pi/180)
let ovalRectNF = CGRect(origin: CGPoint(x: 122.5,y: 83.5), size: CGSize(width: 100, height: 100))
// Create Bezier Path
let ovalPathNF = UIBezierPath()
// Apply Attributes to Bezier Path
ovalPathNF.addArc(withCenter: CGPoint(x: ovalRectNF.midX, y: ovalRectNF.midY),
radius: ovalRectNF.width / 2,
startAngle: ovalStartAngleNF,
endAngle: ovalEndAngleNF, clockwise: true)
// Styling of the Curve
let progressLineNF = CAShapeLayer()
progressLineNF.path = ovalPathNF.cgPath
progressLineNF.strokeColor = colorNF.cgColor
progressLineNF.fillColor = UIColor.clear.cgColor
progressLineNF.lineWidth = 20.0
progressLineNF.lineCap = kCALineCapRound
// Add Curve to Viewport
self.view.layer.addSublayer(progressLineNF)
// Animated the 'Stroke End' from 0 to 1 over 3 Seconds
let animateStrokeEndNF = CABasicAnimation(keyPath: "strokeEnd")
animateStrokeEndNF.duration = 3.0
animateStrokeEndNF.fromValue = 0.0
animateStrokeEndNF.toValue = 1.0
// Add the Animation to the Progress Line
progressLineNF.add(animateStrokeEndNF, forKey: "animate stroke end animation")
As stated by Joe in the comments. The solution was to place my animation code into a viewWillAppear.
override func viewWillAppear(_ animated: Bool) { *ANIMATION CODE* }

add and remove a subview using a button

Hey guys so I've drawn a circle and added it as a subview using a button. When I try to remove it using the removefromsuperview it didn't go away. Please check the code below.
I added a button named Removecircle so I can add or remove the circle but that didn't go as planned.
import UIKit
import GLKit
class ViewController: UIViewController {
var numb = 0
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func Removecircle(_ sender: Any) {
let radius = Int( view.frame.maxX )
let HvalueX = 0
let HvalueY = Int( view.frame.maxY )/2 - Int( view.frame.maxX )/2
// Create a new CircleView
let circleView = CircleView( frame:CGRect(x: HvalueX, y: HvalueY, width: radius, height: radius ))
//let test = CircleView(frame: CGRect(x: diceRoll, y: 0, width: circleWidth, height: circleHeight))
if numb%2 == 0 {
view.addSubview(circleView)
// Animate the drawing of the circle over the course of 1 second
circleView.animateCircle(duration: 4.0)
circleView.circleLayer.strokeColor = UIColor.blue.cgColor
}else if numb%2 == 1 {
circleView.removeFromSuperview()
}
numb = numb + 1
}
#IBOutlet weak var removecircle: UIButton!
}
class CircleView: UIView {
var circleLayer: CAShapeLayer!
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.clear
// Use UIBezierPath as an easy way to create the CGPath for the layer.
// The path should be the entire circle.
let circlePath = UIBezierPath(arcCenter: CGPoint(x: frame.size.width / 2.0, y: frame.size.height / 2.0), radius: (frame.size.width - 10)/2, startAngle: 0.0, endAngle: CGFloat(M_PI * 2.0), clockwise: true)
// Setup the CAShapeLayer with the path, colors, and line width
circleLayer = CAShapeLayer()
circleLayer.path = circlePath.cgPath
circleLayer.fillColor = UIColor.clear.cgColor
circleLayer.strokeColor = UIColor.white.cgColor
circleLayer.lineWidth = 3.0;
// Don't draw the circle initially
circleLayer.strokeEnd = 0.0
// Add the circleLayer to the view's layer's sublayers
layer.addSublayer(circleLayer)
animateCircle(duration: 2)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func animateCircle(duration: TimeInterval) {
// We want to animate the strokeEnd property of the circleLayer
let animation = CABasicAnimation(keyPath: "strokeEnd")
// Set the animation duration appropriately
animation.duration = duration
// Animate from 0 (no circle) to 1 (full circle)
animation.fromValue = 0
animation.toValue = 1
// Do a linear animation (i.e. the speed of the animation stays the same)
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
// Set the circleLayer's strokeEnd property to 1.0 now so that it's the
// right value when the animation ends.
circleLayer.strokeEnd = 1.0
// Do the actual animation
circleLayer.add(animation, forKey: "animateCircle")
}
}
You’re creating a new CircleView every time you press the button. Instead, create it the first time and store it as a property of ViewController. Then when you press the button you should be able to remove it with removeFromSuperview().

Argument passed to call that takes no arguments

I am adding in a animated circle to a uiview. In this line of code:
var circleView = addCircleView(frame: CGRectMake(diceRoll, 0, circleWidth, circleHeight))
I am getting an error that reads "Argument passed to call that takes no arguments" pointing to CGRectMake.
I have aisles attached the rest of the code incase it is needed
import UIKit
import CoreMotion
import CoreGraphics
class Animation: UIView {
var circleLayer: CAShapeLayer!
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.clear
// Use UIBezierPath as an easy way to create the CGPath for the layer.
// The path should be the entire circle.
let circlePath = UIBezierPath(arcCenter: CGPoint(x: frame.size.width / 2.0, y: frame.size.height / 2.0), radius: (frame.size.width - 10)/2, startAngle: 0.0, endAngle: CGFloat(M_PI * 2.0), clockwise: true)
// Setup the CAShapeLayer with the path, colors, and line width
circleLayer = CAShapeLayer()
circleLayer.path = circlePath.cgPath
circleLayer.fillColor = UIColor.clear.cgColor
circleLayer.strokeColor = UIColor.red.cgColor
circleLayer.lineWidth = 5.0;
// Don't draw the circle initially
circleLayer.strokeEnd = 0.0
// Add the circleLayer to the view's layer's sublayers
layer.addSublayer(circleLayer)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func animateCircle(duration: TimeInterval) {
// We want to animate the strokeEnd property of the circleLayer
let animation = CABasicAnimation(keyPath: "strokeEnd")
// Set the animation duration appropriately
animation.duration = duration
// Animate from 0 (no circle) to 1 (full circle)
animation.fromValue = 0
animation.toValue = 1
// Do a linear animation (i.e. the speed of the animation stays the same)
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
// Set the circleLayer's strokeEnd property to 1.0 now so that it's the
// right value when the animation ends.
circleLayer.strokeEnd = 1.0
// Do the actual animation
circleLayer.add(animation, forKey: "animateCircle")
}
func addCircleView() {
let diceRoll = CGFloat(Int(arc4random_uniform(7))*50)
var circleWidth = CGFloat(200)
var circleHeight = circleWidth
// Create a new CircleView
var circleView = addCircleView(frame: CGRectMake(diceRoll, 0, circleWidth, circleHeight))
UIView.addSubview(circleView)
// Animate the drawing of the circle over the course of 1 second
circleView.animateCircle(1.0)
}
}
Credits to Mike S
While I'd change all references of CGRectMake to CGRect, the issue is with the call to addCircle(). You didn't define any arguments.
Try changing things to:
func addCircleView(frame: CGRect) {
Or, since addCircleView looks like it doesn't use this parameter, try removing the CGRect/CGRectMake from the call to addCircle():
var circleView = addCircleView()
(It looks like you probably want the former.)

Resources