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.)
Related
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()
I want to expand and contract a circle using CABasicAnimation. I previously designed a clean and effective way to draw the animation using sin and the drawRect method of the UIView class, but unfortunately it seems you can't animate or force drawRect to fire in UIView.animate methods no matter what you do. Therefore, I've had to come up with a workaround using CABasicAnimation. The result kind of works, but the animation is ugly and doesn't look right. Here's an illustration of what I mean:
Warping occurs during the expansion and contraction, and there appears to be a dent or cavity on the right side when the circle is expanding. Here's the class responsible for animating it:
struct PathConfig {
var arcCenter: CGPoint
var radius: CGFloat
let startAngle: CGFloat = 0.0
let endAngle: CGFloat = CGFloat(2 * M_PI)
}
class RGSStandbyIndicatorView: UIView {
// MARK: - Variables & Constants
/// The drawing layer.
var shapeLayer: CAShapeLayer!
// MARK: - Class Methods
/// Animation function.
func animate(period: CFTimeInterval) {
let p: PathConfig = PathConfig(arcCenter: CGPoint(x: frame.width / 2, y: frame.height / 2) , radius: 17.5)
let b: UIBezierPath = UIBezierPath(arcCenter: p.arcCenter, radius: p.radius, startAngle: p.startAngle, endAngle: p.endAngle, clockwise: true)
let a: CABasicAnimation = CABasicAnimation(keyPath: "path")
a.fromValue = shapeLayer.path
a.toValue = b.cgPath
a.autoreverses = true
a.duration = period
a.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
a.repeatCount = HUGE
shapeLayer.add(a, forKey: "animateRadius")
}
/// Initializes and returns a CAShapeLayer with the given draw path and fill color.
private class func shapeLayerForPath(_ path: CGPath, with fillColor: CGColor) -> CAShapeLayer {
let shapeLayer: CAShapeLayer = CAShapeLayer()
shapeLayer.path = path
shapeLayer.fillColor = fillColor
return shapeLayer
}
/// Configures the UIView
private func setup() {
// Init config, path.
let cfg: PathConfig = PathConfig(arcCenter: CGPoint(x: frame.width / 2, y: frame.height / 2) , radius: 0.0)
let path: UIBezierPath = UIBezierPath(arcCenter: cfg.arcCenter, radius: cfg.radius, startAngle: cfg.startAngle, endAngle: cfg.endAngle, clockwise: true)
// Init CAShapeLayer
shapeLayer = RGSStandbyIndicatorView.shapeLayerForPath(path.cgPath, with: UIColor.white.cgColor)
layer.addSublayer(shapeLayer)
}
// MARK: Class Method Overrides
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
}
I've tried adjusting the start and end angles hoping it was some artifact of the path having no overlap, but it didn't change anything. I'm not sure how to fix this problem, and It's based off Chadwick Wood's answer to a similar question.
Use this UIView subclass
class RadioAnimationView: UIView {
var animatableLayer : CAShapeLayer?
override func awakeFromNib() {
super.awakeFromNib()
self.layer.cornerRadius = self.bounds.height/2
self.animatableLayer = CAShapeLayer()
self.animatableLayer?.fillColor = self.backgroundColor?.cgColor
self.animatableLayer?.path = UIBezierPath(roundedRect: self.bounds, cornerRadius: self.layer.cornerRadius).cgPath
self.animatableLayer?.frame = self.bounds
self.animatableLayer?.cornerRadius = self.bounds.height/2
self.animatableLayer?.masksToBounds = true
self.layer.addSublayer(self.animatableLayer!)
self.startAnimation()
}
func startAnimation()
{
let layerAnimation = CABasicAnimation(keyPath: "transform.scale")
layerAnimation.fromValue = 1
layerAnimation.toValue = 3
layerAnimation.isAdditive = false
layerAnimation.duration = CFTimeInterval(2)
layerAnimation.fillMode = kCAFillModeForwards
layerAnimation.isRemovedOnCompletion = true
layerAnimation.repeatCount = .infinity
layerAnimation.autoreverses = true
self.animatableLayer?.add(layerAnimation, forKey: "growingAnimation")
}
}
Hope this helps you
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
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().
This is going to be a really basic question.
I am working through this answer:
Animate drawing of a circle
But no matter how I format it I get an error. I can see from the error that I have not initialized circle and I am sure it is just a positioning thing but not sure what or how to do that correctly or what is wrong with how I have layout.
When I try like this i get the error ('self.circleLayer' not initialized at super.init call):
import UIKit
class CircleView: UIView {
let circleLayer: CAShapeLayer!
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.clearColor()
// 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.clearColor().CGColor
circleLayer.strokeColor = UIColor.redColor().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")
}
}
Then tried moving it to after the initializer like this which doesn't give me an error):
import UIKit
class CircleView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
let circleLayer: CAShapeLayer!
self.backgroundColor = UIColor.clearColor()
// 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.clearColor().CGColor
circleLayer.strokeColor = UIColor.redColor().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")
}
}
But then when I try to put a function in my viewcontroller.swift that references circleLayer I get unresolved identifier:
func animateCircle(duration: NSTimeInterval) {
// 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.addAnimation(animation, forKey: "animateCircle")
}
I am sure it just something really simple but I am not sure what.
Thanks for your help.
From the documentation
Safety check 1
A designated initializer must ensure that all of the
properties introduced by its class are initialized before it delegates
up to a superclass initializer.
Initialize circleLayer in the declaration line and move self.backgroundColor = ... after super.init
class CircleView: UIView {
let circleLayer = CAShapeLayer()
override init(frame: CGRect) {
// 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)
super.init(frame: frame)
// Setup the CAShapeLayer with the path, colors, and line width
self.backgroundColor = UIColor.clearColor()
circleLayer.path = circlePath.CGPath
circleLayer.fillColor = UIColor.clearColor().CGColor
circleLayer.strokeColor = UIColor.redColor().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")
}
}