Animación de múltiples UIView con UIBezierPath

Tengo debajo de la pantalla:

introduzca la descripción de la imagen aquí

Cuando hago clic en el button Obtener más, agregará un nuevo button networkingondeado. UIView Pan Gesture en UIView donde se UIView todos los botones networkingondeados que dibujarán un UIBezierPath desde el button Redondeado. Hasta este punto, todo funciona.

Ahora quiero lo siguiente:

  1. Tengo padre UIView llamado playGroundView, que contiene todo el button networkingondeado. Esto dibujará una ruta Bezier para todos esos botones.
  2. Una vez que se dibuja Bezier Path para todos los botones, hago clic en el button Mover para animar todo el button networkingondeado a la vez.
  3. Una vez, todos los botones se mueven en el punto final de Bezier Path, entonces puedo volver a dibujar más Bezier Path (agregar más pasos para el button Rounded) a la misma ruta para el mismo button.
  4. Después de esto, el button Final Move animará todo para el mismo button a la vez.

Consulte el siguiente código para la subclass PlayGround UIView:

 @interface PlayGroundView : UIView @property (nonatomic, weak) PlayGroundViewController *playViewController; @end #import "PlayGroundView.h" #import "RoundedButton.h" #import "PlayGroundViewController.h" @interface PlayGroundView () { UIBezierPath *path; BOOL isDrawPointInside; } @end @implementation PlayGroundView #pragma mark - View Methods - - (id)initWithCoder:(NSCoder *)aDecoder { if (self = [super initWithCoder:aDecoder]) [self commonInit]; return self; } - (id)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) [self commonInit]; return self; } - (void)drawRect:(CGRect)rect { [[UIColor networkingColor] setStroke]; [path stroke]; } #pragma mark - Action Methods - - (void)pan:(UIPanGestureRecognizer *)pan { CGPoint currentPoint = [pan locationInView:self]; // NSLog(@"currentPoint: %@", NSStringFromCGPoint(currentPoint)); if (pan.state == UIGestureRecognizerStateBegan) { NSMutableArray *buttonAry = [NSMutableArray array]; buttonAry = self.playViewController.rBtnOpponentPlayerList; [buttonAry addObjectsFromArray:self.playViewController.rBtnPlayerList]; for (int i=0; i<buttonAry.count; i++) { UIButton *btn = [buttonAry objectAtIndex:i]; CGRect nearByRect = CGRectMake(btn.frame.origin.x - 20, btn.frame.origin.y - 20, btn.frame.size.width + 40, btn.frame.size.height + 40); if (CGRectContainsPoint(nearByRect, currentPoint)) { isDrawPointInside = YES; [path moveToPoint:btn.center]; // NSLog(@"point is inside...."); } } } if (pan.state == UIGestureRecognizerStateChanged) { if (isDrawPointInside) { [path addLineToPoint:currentPoint]; [self setNeedsDisplay]; } } if (pan.state == UIGestureRecognizerStateEnded) { isDrawPointInside = NO; // NSLog(@"pan ended"); } } #pragma mark - Helper Methods - - (void)commonInit { path = [UIBezierPath bezierPath]; path.lineWidth = 3.0; // Capture touches UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)]; pan.maximumNumberOfTouches = pan.minimumNumberOfTouches = 1; [self addGestureRecognizer:pan]; // Erase with long press [self addGestureRecognizer:[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(erase)]]; } - (void)erase { path = [UIBezierPath bezierPath]; path.lineWidth = 3.0; [self setNeedsDisplay]; } @end 

Todavía no puedo gestionar todo el estado del button networkingondeado. Déjame saber la mejor manera de administrar todo el paso del button networkingondeado mientras te mueves.

Me he referido a: http://nachbaur.com/blog/core-animation-part-4

Mi código de demostración completo está disponible en: https://www.dropbox.com/s/f0ri0bff8ioxtpz/FreeHandDrawingDemo%202.zip?dl=0

Asumiré que cuando dices "gestionar todo el estado del button networkingondeado", quieres decir que quieres que la vista viewHasMovedABitAutomatically animar, algo así como una vista viewHasMovedABitAutomatically 🙂 Si ese es el caso, por lo que yo sé no hay una forma implementada en el marco de CA para hacerlo. Sin embargo, eche un vistazo a esta pregunta: callback de progreso de animation central Aquí hay una manera de hacer una solución que se ve muy bien, tal vez pueda trabajar en algo a partir de aquí.

Ah, y una cosa. Veo que estás usando un reconocedor de gestos Pan. Acabo de hojear tanto su código y tarmes '(el tipo en el otro hilo;)) pero creo que está usando drawInContext para comprobar que la capa está siendo dibujada. Esto también puede desencadenarse si su vista es arrastrable, de modo que, si ese es el caso, considere usar alguna bandera viewIsBeingDragged en el método pan y viewIsBeingDragged en la callback.

EDIT: Lo siento, eso parece no tener nada que ver con tu problema real. Entiendo que lo que quieres decir es controlar la velocidad de la animation para que todos se muevan en consecuencia. Vamos a elaborar sobre eso.

Por lo que sé, no hay tal cosa como un "paso" de animation. Animar una image para moverlo 20 píxeles a la izquierda no implica necesariamente que la image se vuelva a dibujar 20 veces. La image se representará tantas veces como sea necesario, por lo que no podemos confiar en el método render para calcularlo. Por lo que sé, la única forma de controlar la velocidad de la animation es a través del parámetro de duration de animateWithDuration: Y no sabe la duración, quiere que la velocidad sea constante, por lo que la duración es lo que sea necesario.

La física básica: la duración es igual a la distancia dividida por la velocidad, así que si tuviéramos la distancia que necesitamos cubrir podríamos calcular fácilmente la duración de la animation. Al principio pensé que tal vez UIBezierPath tenía un método o un atributo para calcular la longitud total, pero estaba equivocado. Sin embargo, en este hilo Obtener la longitud total de CGPath hay un código que lo calcula por integración numérica, debe probarlo para ver si funciona. Una vez que lo tienes, puedes hacer algo como esto (no lo probé, solo estoy ingresando un pseudocódigo)

 #define SPEED_CALIBRATION 30.0 // or whatever -(void)animateView:(UIView*)view withBezierPath:(UIBezierPath*)path { CGFloat distance = [self getBezierPathDistance:path]; CGFloat duration = distance / SPEED_CALIBRATION; [self animateView:view withDuration:duration andBezierPath:path]; } 

Algo como esto. getBezierPathDistance es el método del subprocess anterior, suponiendo que funcione, y animateView:withDuration:andBezierPath: es su método para realizar la animation no lineal utilizando la documentation de CA (no recuerdo todos los detalles, pero recuerdo que era una gran cantidad de código;)).

Lo siento si la respuesta es un poco vaga, ¡esperemos que ayude!

Para lograrlo, he usado la categoría UIBezierPath + Points. Aparte de eso, en mi código se utilizan las siguientes classs y lógica:

En PlayGroundView, edité el código en el método drawRect, el código de reposo es el mismo:

 - (void)drawRect:(CGRect)rect { [[UIColor networkingColor] setStroke]; for (int i=0; i<[self subviews].count; i++) { RoundButton *tmpPlayerButton = [self.subviews objectAtIndex:i]; for (UIBezierPath *bezierPath in tmpPlayerButton.allPathsOfPlayer) { [bezierPath strokeWithBlendMode:kCGBlendModeNormal alpha:1.0]; } } [path stroke]; } 

En mi class ViewController, en Obtener más botones debajo del código va:

 - (IBAction)getBallPressed:(id)sender { [self moveBalls]; // self.playView.viewController = self; int xPos = self.view.frame.size.width-30; int yPos = self.view.frame.size.height-30; int btnXPos = arc4random_uniform(xPos); int btnYPos = arc4random_uniform(yPos); RoundButton *newButton = [[RoundButton alloc] initWithFrame:CGRectMake(btnXPos, btnYPos, 30, 30)]; [newButton setBackgroundColor:[UIColor networkingColor]]; newButton.layer.cornerRadius = newButton.frame.size.height/2; [newButton setTitle:[self randomStringWithLength:1] forState:UIControlStateNormal]; UILongPressGestureRecognizer *longGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longGestureRecognized:)]; [newButton addGestureRecognizer:longGesture]; [self.playView.playerArray addObject:newButton]; [self.playView addSubview:newButton]; } - (void)moveBalls { for (int i=0; i<[self.playView subviews].count; i++) { RoundButton *playerButton = [self.playView.subviews objectAtIndex:i]; if (playerButton.isEditPath) { id point = [[[playerButton.allPathsOfPlayer lastObject] points] lastObject]; CGPoint lastPoint = [point CGPointValue]; NSLog(@"lastPoint: %@", NSStringFromCGPoint(lastPoint)); [playerButton setCenter:lastPoint]; } } } 

En el button Mover, debajo del código, que me permite mover todo mi Botón networkingondo al mismo time, que es mi requisito Exacto:

 - (void)playAnimation { for (int i=0; i<self.playView.subviews.count; i++) { //Prepare the animation - we use keyframe animation for animations of this complexity CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"]; //Set some variables on the animation pathAnimation.calculationMode = kCAAnimationPaced; //We want the animation to persist - not so important in this case - but kept for clarity //If we animated something from left to right - and we wanted it to stay in the new position, //then we would need these parameters pathAnimation.fillMode = kCAFillModeForwards; pathAnimation.removedOnCompletion = NO; pathAnimation.duration = 2.0; pathAnimation.repeatCount = 0; RoundButton *tmpRoundButton; UIBezierPath *path; tmpRoundButton = [self.playView.subviews objectAtIndex:i]; for (int j=0; j<tmpRoundButton.allPathsOfPlayer.count; j++) { if (path) [path appendPath:[tmpRoundButton.allPathsOfPlayer objectAtIndex:j]]; else path = [tmpRoundButton.allPathsOfPlayer objectAtIndex:j]; //Now we have the path, we tell the animation we want to use this path - then we release the path pathAnimation.path = [path CGPath]; [tmpRoundButton.layer addAnimation:pathAnimation forKey:@"moveTheSquare"]; } [path moveToPoint:[[[path points] firstObject] CGPointValue]]; } [self.playView setNeedsDisplay]; }