Crear una propiedad animable personalizada

En UIView puedes cambiar el backgroundColour animado. Y en UISlideView puedes cambiar el valor animado.

¿Puedes agregar una propiedad personalizada a tu propia subclase UIView para que pueda ser animada?

Si tengo un CGPath dentro de mi UIView entonces puedo animar el dibujo cambiando el porcentaje dibujado de la ruta.

¿Puedo encapsular esa animación en la subclase?

es decir, tengo una UIView con un CGPath que crea un círculo.

Si el círculo no está allí, representa 0%. Si el círculo está lleno, representa el 100%. Puedo dibujar cualquier valor cambiando el porcentaje dibujado de la ruta. También puedo animar el cambio (dentro de la subclase UIView) animando el porcentaje de CGPath y redibujando la ruta.

¿Puedo establecer alguna propiedad (es decir, porcentaje) en la UIView para poder UIView el cambio en un bloque UIView animateWithDuration y animar el cambio del porcentaje de la ruta?

Espero haber explicado lo que me gustaría hacer bien.

Esencialmente, todo lo que quiero hacer es algo así como …

 [UIView animateWithDuration:1.0 animations:^{ myCircleView.percentage = 0.7; } completion:nil]; 

y la trayectoria circular anima al porcentaje dado.

Si extiende CALayer e implementa su medida personalizada

 - (void) drawInContext:(CGContextRef) context 

Puede crear una propiedad animable anulando needsDisplayForKey (en su clase CALayer personalizada) de esta manera:

 + (BOOL) needsDisplayForKey:(NSString *) key { if ([key isEqualToString:@"percentage"]) { return YES; } return [super needsDisplayForKey:key]; } 

Por supuesto, también necesita tener un @property llamado percentage . De ahora en adelante, puedes animar la propiedad de porcentaje usando la animación central. No [UIView animateWithDuration...] si funciona usando la [UIView animateWithDuration...] también. Podría funcionar. Pero esto funcionó para mí:

 CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"percentage"]; animation.duration = 1.0; animation.fromValue = [NSNumber numberWithDouble:0]; animation.toValue = [NSNumber numberWithDouble:100]; [myCustomLayer addAnimation:animation forKey:@"animatePercentage"]; 

Ah, y para usar yourCustomLayer con myCircleView , haz esto:

 [myCircleView.layer addSublayer:myCustomLayer]; 

Ejemplo completo de Swift 3:

Producto final

 public class CircularProgressView: UIView { public dynamic var progress: CGFloat = 0 { didSet { progressLayer.progress = progress } } fileprivate var progressLayer: CircularProgressLayer { return layer as! CircularProgressLayer } override public class var layerClass: AnyClass { return CircularProgressLayer.self } override public func action(for layer: CALayer, forKey event: String) -> CAAction? { if event == #keyPath(CircularProgressLayer.progress), let action = action(for: layer, forKey: #keyPath(backgroundColor)) as? CAAnimation { let animation = CABasicAnimation() animation.keyPath = #keyPath(CircularProgressLayer.progress) animation.fromValue = progressLayer.progress animation.toValue = progress animation.beginTime = action.beginTime animation.duration = action.duration animation.speed = action.speed animation.timeOffset = action.timeOffset animation.repeatCount = action.repeatCount animation.repeatDuration = action.repeatDuration animation.autoreverses = action.autoreverses animation.fillMode = action.fillMode animation.timingFunction = action.timingFunction animation.delegate = action.delegate self.layer.add(animation, forKey: #keyPath(CircularProgressLayer.progress)) } return super.action(for: layer, forKey: event) } } /* * Concepts taken from: * https://stackoverflow.com/a/37470079 */ fileprivate class CircularProgressLayer: CALayer { @NSManaged var progress: CGFloat let startAngle: CGFloat = 1.5 * .pi let twoPi: CGFloat = 2 * .pi let halfPi: CGFloat = .pi / 2 override class func needsDisplay(forKey key: String) -> Bool { if key == #keyPath(progress) { return true } return super.needsDisplay(forKey: key) } override func draw(in ctx: CGContext) { super.draw(in: ctx) UIGraphicsPushContext(ctx) //Light Grey UIColor.lightGray.setStroke() let center = CGPoint(x: bounds.midX, y: bounds.midY) let strokeWidth: CGFloat = 4 let radius = (bounds.size.width / 2) - strokeWidth let path = UIBezierPath(arcCenter: center, radius: radius, startAngle: 0, endAngle: twoPi, clockwise: true) path.lineWidth = strokeWidth path.stroke() //Red UIColor.red.setStroke() let endAngle = (twoPi * progress) - halfPi let pathProgress = UIBezierPath(arcCenter: center, radius: radius, startAngle: startAngle, endAngle: endAngle , clockwise: true) pathProgress.lineWidth = strokeWidth pathProgress.lineCapStyle = .round pathProgress.stroke() UIGraphicsPopContext() } } let circularProgress = CircularProgressView(frame: CGRect(x: 0, y: 0, width: 80, height: 80)) UIView.animate(withDuration: 2, delay: 0, options: .curveEaseInOut, animations: { circularProgress.progress = 0.76 }, completion: nil) 

Aquí hay un gran artículo objc, que detalla cómo funciona esto

Además de un proyecto objc que usa los mismos conceptos aquí :

Esencialmente se llamará a la acción (para la capa 🙂 cuando se está animando un objeto desde un bloque de animación, podemos comenzar nuestras propias animaciones con las mismas propiedades (robadas de la propiedad backgroundColor) y animar los cambios.

Para los que necesitan más detalles sobre eso como yo lo hice:

hay un buen ejemplo de Apple que cubre esta pregunta.

Por ejemplo, gracias a esto, descubrí que en realidad no necesitas agregar tu capa personalizada como subcapa (como sugiere @Tom van Zummeren ). En cambio, es suficiente para agregar un método de clase a su clase de Vista:

 + (Class)layerClass { return [CustomLayer class]; } 

Espero que ayude a alguien.

tendrá que implementar la parte de porcentaje usted mismo. puede anular el código de dibujo de la capa para dibujar su camino de CG accediendo al valor de porcentaje establecido. consulte la guía de progtwigción de animación central y los tipos de animación y la guía de sincronización