swift circle geometry uiviewanimation progress drawrect

swift - Dibujo animado de un círculo



circle geometry (4)

¡La respuesta de Mike es genial! Otra forma agradable y sencilla de hacerlo es usar drawRect combinado con setNeedsDisplay (). Parece lento, pero no es :-)

Queremos dibujar un círculo comenzando desde la parte superior, que es -90 ° y termina en 270 °. El centro del círculo es (centerX, centerY), con un radio dado. CurrentAngle es el ángulo actual del punto final del círculo, que va de minAngle (-90) a maxAngle (270).

// MARK: Properties let centerX:CGFloat = 55 let centerY:CGFloat = 55 let radius:CGFloat = 50 var currentAngle:Float = -90 let minAngle:Float = -90 let maxAngle:Float = 270

En drawRect, especificamos cómo se debe mostrar el círculo:

override func drawRect(rect: CGRect) { let context = UIGraphicsGetCurrentContext() let path = CGPathCreateMutable() CGPathAddArc(path, nil, centerX, centerY, radius, CGFloat(GLKMathDegreesToRadians(minAngle)), CGFloat(GLKMathDegreesToRadians(currentAngle)), false) CGContextAddPath(context, path) CGContextSetStrokeColorWithColor(context, UIColor.blueColor().CGColor) CGContextSetLineWidth(context, 3) CGContextStrokePath(context) }

El problema es que en este momento, como currentAngle no está cambiando, el círculo es estático y ni siquiera se muestra, como currentAngle = minAngle.

Luego creamos un temporizador, y cada vez que se dispara ese temporizador, aumentamos el ángulo actual. En la parte superior de su clase, agregue el tiempo entre dos incendios:

let timeBetweenDraw:CFTimeInterval = 0.01

En su init, agregue el temporizador:

NSTimer.scheduledTimerWithTimeInterval(timeBetweenDraw, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true)

Podemos agregar la función que se llamará cuando se active el temporizador:

func updateTimer() { if currentAngle < maxAngle { currentAngle += 1 } }

Lamentablemente, al ejecutar la aplicación, no se muestra nada porque no especificamos el sistema que debería volver a dibujar. Esto se hace llamando a setNeedsDisplay (). Aquí está la función de temporizador actualizada:

func updateTimer() { if currentAngle < maxAngle { currentAngle += 1 setNeedsDisplay() } }

_ _ _

Todo el código que necesita se resume aquí:

import UIKit import GLKit class CircleClosing: UIView { // MARK: Properties let centerX:CGFloat = 55 let centerY:CGFloat = 55 let radius:CGFloat = 50 var currentAngle:Float = -90 let timeBetweenDraw:CFTimeInterval = 0.01 // MARK: Init required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) setup() } override init(frame: CGRect) { super.init(frame: frame) setup() } func setup() { self.backgroundColor = UIColor.clearColor() NSTimer.scheduledTimerWithTimeInterval(timeBetweenDraw, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true) } // MARK: Drawing func updateTimer() { if currentAngle < 270 { currentAngle += 1 setNeedsDisplay() } } override func drawRect(rect: CGRect) { let context = UIGraphicsGetCurrentContext() let path = CGPathCreateMutable() CGPathAddArc(path, nil, centerX, centerY, radius, -CGFloat(M_PI/2), CGFloat(GLKMathDegreesToRadians(currentAngle)), false) CGContextAddPath(context, path) CGContextSetStrokeColorWithColor(context, UIColor.blueColor().CGColor) CGContextSetLineWidth(context, 3) CGContextStrokePath(context) } }

Si desea cambiar la velocidad, simplemente modifique la función updateTimer o la velocidad a la que se llama a esta función. Además, es posible que desee invalidar el temporizador una vez que se complete el círculo, lo que olvidé hacer :-)

NB: Para agregar el círculo en su guión gráfico, simplemente agregue una vista, selecciónela, vaya a su Inspector de identidad y, como Clase , especifique CircleClosing .

¡Salud! bRo

Estoy buscando una forma de animar el dibujo de un círculo. He podido crear el círculo, pero lo une todo.

Aquí está mi clase CircleView :

import UIKit class CircleView: UIView { override init(frame: CGRect) { super.init(frame: frame) self.backgroundColor = UIColor.clearColor() } required init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func drawRect(rect: CGRect) { // Get the Graphics Context var context = UIGraphicsGetCurrentContext(); // Set the circle outerline-width CGContextSetLineWidth(context, 5.0); // Set the circle outerline-colour UIColor.redColor().set() // Create Circle CGContextAddArc(context, (frame.size.width)/2, frame.size.height/2, (frame.size.width - 10)/2, 0.0, CGFloat(M_PI * 2.0), 1) // Draw CGContextStrokePath(context); } }

Y así es como lo agrego a la jerarquía de vistas en mi controlador de vista:

func addCircleView() { let diceRoll = CGFloat(Int(arc4random_uniform(7))*50) var circleWidth = CGFloat(200) var circleHeight = circleWidth // Create a new CircleView var circleView = CircleView(frame: CGRectMake(diceRoll, 0, circleWidth, circleHeight)) view.addSubview(circleView) }

¿Hay alguna forma de animar el dibujo del círculo durante 1 segundo?

Ejemplo, en la mitad de la animación se vería algo así como la línea azul en esta imagen:


La forma más fácil de hacer esto es utilizar el poder de la animación principal para hacer la mayor parte del trabajo por usted. Para hacer eso, tendremos que mover su código de dibujo circular de su función drawRect a CAShapeLayer . Entonces, podemos usar una CABasicAnimation para animar la propiedad strokeEnd de 0.0 a 1.0 . strokeEnd es una gran parte de la magia aquí; de los documentos:

Combinada con la propiedad strokeStart, esta propiedad define la subregión de la ruta al trazo. El valor en esta propiedad indica el punto relativo a lo largo de la ruta en la que finalizar el trazo mientras la propiedad strokeStart define el punto de inicio. Un valor de 0.0 representa el comienzo de la ruta, mientras que un valor de 1.0 representa el final de la ruta. Los valores intermedios se interpretan linealmente a lo largo de la longitud de la ruta.

Si establecemos strokeEnd en 0.0 , no dibujará nada. Si lo establecemos en 1.0 , dibujará un círculo completo. Si lo establecemos en 0.5 , dibujará un semicírculo. etc.

Entonces, para comenzar, CAShapeLayer un CAShapeLayer en la función init su CircleView y agreguemos esa capa a las sublayers la vista (también asegúrese de eliminar la función drawRect ya que la capa dibujará el círculo ahora):

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) }

Nota: Estamos configurando circleLayer.strokeEnd = 0.0 para que el círculo no se circleLayer.strokeEnd = 0.0 inmediato.

Ahora, agreguemos una función que podemos llamar para activar la animación del círculo:

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") }

Luego, todo lo que tenemos que hacer es cambiar su función addCircleView para que addCircleView la animación cuando agrega CircleView a su superview :

func addCircleView() { let diceRoll = CGFloat(Int(arc4random_uniform(7))*50) var circleWidth = CGFloat(200) var circleHeight = circleWidth // Create a new CircleView var circleView = CircleView(frame: CGRectMake(diceRoll, 0, circleWidth, circleHeight)) view.addSubview(circleView) // Animate the drawing of the circle over the course of 1 second circleView.animateCircle(1.0) }

Todos los que se unan deberían verse así:

Nota: No se repetirá así, permanecerá en un círculo completo después de animarse.


Respuesta de Mikes actualizada para Swift 3.0

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") }

Para llamar a la función:

func addCircleView() { let diceRoll = CGFloat(Int(arc4random_uniform(7))*50) var circleWidth = CGFloat(200) var circleHeight = circleWidth // Create a new CircleView let circleView = CircleView(frame: CGRect(x: diceRoll, y: 0, width: circleWidth, height: circleHeight)) //let test = CircleView(frame: CGRect(x: diceRoll, y: 0, width: circleWidth, height: circleHeight)) view.addSubview(circleView) // Animate the drawing of the circle over the course of 1 second circleView.animateCircle(duration: 1.0) }


Si desea un controlador de finalización, esta es otra solución similar a la de Mike S, realizada en Swift 3.0

func animateCircleFull(duration: TimeInterval) { CATransaction.begin() let animation = CABasicAnimation(keyPath: "strokeEnd") animation.duration = duration animation.fromValue = 0 animation.toValue = 1 animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) circleLayer.strokeEnd = 1.0 CATransaction.setCompletionBlock { print("animation complete") } // Do the actual animation circleLayer.add(animation, forKey: "animateCircle") CATransaction.commit() }

Con el controlador de finalización, puede ejecutar la animación nuevamente, ya sea llamando de forma recursiva a la misma función para volver a hacer la animación (que no se verá muy bien), o puede tener una función inversa que se encadenará continuamente hasta que se cumpla una condición , por ejemplo:

func animate(duration: TimeInterval){ self.isAnimating = true self.animateCircleFull(duration: 1) } func endAnimate(){ self.isAnimating = false } func animateCircleFull(duration: TimeInterval) { if self.isAnimating{ CATransaction.begin() let animation = CABasicAnimation(keyPath: "strokeEnd") animation.duration = duration animation.fromValue = 0 animation.toValue = 1 animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) circleLayer.strokeEnd = 1.0 CATransaction.setCompletionBlock { self.animateCircleEmpty(duration: duration) } // Do the actual animation circleLayer.add(animation, forKey: "animateCircle") CATransaction.commit() } } func animateCircleEmpty(duration: TimeInterval){ if self.isAnimating{ CATransaction.begin() let animation = CABasicAnimation(keyPath: "strokeEnd") animation.duration = duration animation.fromValue = 1 animation.toValue = 0 animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) circleLayer.strokeEnd = 0 CATransaction.setCompletionBlock { self.animateCircleFull(duration: duration) } // Do the actual animation circleLayer.add(animation, forKey: "animateCircle") CATransaction.commit() } }

Para hacerlo aún más elegante, puede cambiar la dirección de la animación de esta manera:

func setCircleClockwise(){ 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) self.circleLayer.removeFromSuperlayer() self.circleLayer = formatCirle(circlePath: circlePath) self.layer.addSublayer(self.circleLayer) } func setCircleCounterClockwise(){ 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: false) self.circleLayer.removeFromSuperlayer() self.circleLayer = formatCirle(circlePath: circlePath) self.layer.addSublayer(self.circleLayer) } func formatCirle(circlePath: UIBezierPath) -> CAShapeLayer{ let circleShape = CAShapeLayer() circleShape.path = circlePath.cgPath circleShape.fillColor = UIColor.clear.cgColor circleShape.strokeColor = UIColor.red.cgColor circleShape.lineWidth = 10.0; circleShape.strokeEnd = 0.0 return circleShape } func animate(duration: TimeInterval){ self.isAnimating = true self.animateCircleFull(duration: 1) } func endAnimate(){ self.isAnimating = false } func animateCircleFull(duration: TimeInterval) { if self.isAnimating{ CATransaction.begin() let animation = CABasicAnimation(keyPath: "strokeEnd") animation.duration = duration animation.fromValue = 0 animation.toValue = 1 animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) circleLayer.strokeEnd = 1.0 CATransaction.setCompletionBlock { self.setCircleCounterClockwise() self.animateCircleEmpty(duration: duration) } // Do the actual animation circleLayer.add(animation, forKey: "animateCircle") CATransaction.commit() } } func animateCircleEmpty(duration: TimeInterval){ if self.isAnimating{ CATransaction.begin() let animation = CABasicAnimation(keyPath: "strokeEnd") animation.duration = duration animation.fromValue = 1 animation.toValue = 0 animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) circleLayer.strokeEnd = 0 CATransaction.setCompletionBlock { self.setCircleClockwise() self.animateCircleFull(duration: duration) } // Do the actual animation circleLayer.add(animation, forKey: "animateCircle") CATransaction.commit() } }