Agregar subview más tarde para animar junto con supervise

Tengo una vista de contenedor, llamémosla vista de zócalo , que tiene una sola subvista, es la vista de contenido, llamémosle vista de complemento . Esta vista de conector puede ser nula, es decir, la vista de socket está actualmente vacía. Si contiene una vista de complemento, ocupa todo el espacio del conector, es decir, su estructura corresponde a los límites del conector. Desde una perspectiva externa, ni siquiera debería ser capaz de decir que en realidad hay dos vistas, ya que la vista de complemento siempre está exactamente donde está el conector.

Estoy luchando para que las animaciones funcionen correctamente: si la vista de conector existe y se distribuye antes de la animation, todo funciona como se esperaba. Sin embargo, si configuro la vista del conector del socket solo cuando la animation ya está ejecutándose, obtengo un efecto no deseado:

La vista del conector se muestra en el lugar donde estaría al final de la animation y no se anima junto con su zócalo. Me gustaría que parezca que ha estado allí todo el time, pero que solo se hizo visible en este momento, es decir, la vista de complemento (y sus subvistas) debería animarse junto con el conector, incluso si lo agrego mientras la animation está en progreso.

¿Cómo puedo lograr este comportamiento?

Mis ideas: obviamente, la vista del conector debe ser presentada dos veces: una vez para su position final, y una vez más para donde la vista de socket comenzó a animarse, o donde se agregó. Podría calcular este cuadro, aplicarlo sin animation y animar al cuadro final dentro de un nuevo bloque de animation. Para que la synchronization de la animation sea consistente, necesitaría tener la misma curva y duración, pero comienzo la animation en el pasado o de alguna manera la adelanté. es posible? ¿Hay otros enfoques para tener la vista del complemento ancho y alto en todo momento ?


Como continuación de la respuesta de Rob, he aquí algunos detalles más de lo que estoy buscando exactamente:

  • La vista de socket está animando porque ha cambiado el tamaño del propietario. Puede pensar en ella como una celda de ancho completo en una vista de tabla.

  • La vista de complemento puede contener sus propias subvenciones a lo largo de los gustos de vistas de imágenes, tags, etc. Estos también deberían join a la animation de la vista de socket, como si siempre hubieran estado allí desde que comenzó la animation.

  • Si bien, en teoría, es posible que una nueva animation comience mientras uno ya está ejecutándose, realmente no me importa el comportamiento en este caso de borde.

  • No es necesario que el usuario pueda interactuar con la vista del complemento mientras la animation se está ejecutando; Esto es más probable que ocurra durante un cambio de orientación de interfaz de todos modos.

  • La vista de complemento puede decidir cambiar su contenido debido a una actualización de model asíncrona mientras se anima, pero nuevamente se trata de un caso de borde y no me importa si la animation no parece perfecta en este caso. Sin embargo, su tamaño no cambia, siempre es el mismo que el tamaño de la vista del conector.

Puedes encontrar mi proyecto de testing aquí.

Dice que su vista de plug-in debe cubrir completamente, exactamente, la vista de zócalo. Debemos preocuparnos por dos cosas: la position de la capa del plug ( layer.position ) y el tamaño de la capa ( layer.bounds.size ).

De manera pnetworkingeterminada, la position controla el centro de la capa (y la vista) porque el punto de anchorPoint pnetworkingeterminado es (0.5, 0.5). Si establecemos anchorPoint en (0, 0), la position controla la esquina superior izquierda de la capa, y siempre queremos que esté en (0, 0) en el sistema de coorderadas del socket. Entonces, al establecer both anchorPoint y position a CGPointZero , podemos evitar preocuparnos por la animation layer.position .

Eso solo nos deja animar layer.bounds.size .

Cuando anima una vista usando animaciones UIKit, crea instancias de CABasicAnimation bajo el capó. CABasicAnimation es una subclass de CAAnimation que agrega (entre otras cosas) de las properties fromValue y toValue , especificando los valores inicial y final de la animation.

CAAnimation cumple con el protocolo CAMediaTiming , lo que significa que tiene una propiedad beginTime . Cuando crea una CAAnimation , tiene un beginTime pnetworkingeterminado de cero. Core Animation cambia eso a la hora actual (ver CACurrentMediaTime ) cuando confirma la transacción actual.

Pero si la animation ya tiene un beginTime distinto de cero, Core Animation lo usa tal cual. Si ese time beginTime en el pasado, la animation ya se ha completado parcialmente (o incluso completamente) cuando aparece por primera vez en la pantalla, y se dibuja con la cantidad de progreso apropiada ya realizada. En esencia, podemos "retroceder" una animation.

Por lo tanto, si CABasicAnimation controlando los CABasicAnimation del socket, y lo agregamos al conector, el conector debería animarse tal como lo deseamos. Cuando adjuntamos la animation al plugin, its beginTime es el momento en que comenzó a animar el socket. Entonces, aunque lo conectemos al conector más tarde, se sincronizará perfectamente con el conector. Y dado que queremos que el conector y el conector tengan el mismo tamaño, los fromValue y toValue también son correctos.

En la aplicación de testing, hice que el zócalo rosa y el zócalo azul. Cada uno es un UIImageView muestra una image con líneas en los bordes, por lo que podemos estar seguros de que las vistas tienen los tamaños correctos en todo momento. Esto es lo que se ve en acción:

demo simple

Hay un giro adicional. Si animas una vista que ya está animando, UIKit no detiene la animation anterior. Añade la segunda animation y ambas animaciones se ejecutan simultáneamente.

Esto funciona porque UIKit usa animaciones aditivas . Cuando se anima el ancho de una vista de 320 a 160, UIKit establece el ancho en 160 inmediatamente y agrega una animation aditiva que va de 160 a 0. Al comienzo de la animation, el ancho aparente es 160 + 160 = 320 y en Al final, el ancho aparente es 160 + 0 = 160.

Cuando UIKit agrega una segunda animation mientras la primera está ejecutándose, los valores de ambas animaciones se agregan al valor aparente utilizado para dibujar la vista en la pantalla. Este es el efecto:

múltiples animaciones

Lo que esto significa para nosotros es que debemos search todas las animaciones de socket con un keyPath de position.size , y copyr todas ellas en el conector. Aquí está el código en mi aplicación de testing:

 - (IBAction)plugWasTapped:(id)sender { if (self.plugView.superview) { [self.plugView removeFromSuperview]; return; } self.plugView.frame = self.socketView.bounds; [self.socketView addSubview:self.plugView]; for (NSString *key in self.socketView.layer.animationKeys) { CAAnimation *rawAnimation = [self.socketView.layer animationForKey:key]; if (![rawAnimation isKindOfClass:[CABasicAnimation class]]) { continue; } CABasicAnimation *animation = (CABasicAnimation *)rawAnimation; if ([animation.keyPath isEqualToString:@"bounds.size"]) { [self.plugView.layer addAnimation:animation forKey:key]; } } } 

Aquí está el resultado con múltiples animaciones simultáneas:

demo con múltiples animaciones

ACTUALIZAR

Obtener la vista completa de la jerarquía de la vista del plug para animar como si hubiera estado allí, en primer lugar, es francamente demasiado trabajo.

Aquí hay una alternativa:

  1. Diseña la vista de plugin en el tamaño original del socket y crea una image de ella (la "image de inicio").
  2. Diseña la vista del complemento en el tamaño final del socket y crea una image (la "image final").
  3. Coloque una vista de image de marcador de position en el zócalo.
  4. Copie la animation de tamaño desde el socket al marcador de position.
  5. Use la animation de tamaño para crear una animation de contenido en el marcador de position que funda su contenido desde la image inicial hasta la image final.
  6. Cuando finalice la animation, reemplace el marcador de position con la vista del conector.

Esto es lo que parece:

prueba de crossfade

Además de la imperfección del fundido cruzado, esta versión no maneja múltiples animaciones que se ejecutan simultáneamente. Puedes encontrar esto en la label crossfade en mi repository (enlazado en la parte superior).

Otro enfoque es simplemente renderizar la vista del conector en su tamaño final y colocarla en el marcador de position sin un fundido cruzado. Se parece a esto:

tramo final demo de imagen

Creo que se ve mejor de esta manera, y esta versión maneja animaciones astackdas. Puedes encontrar esto debajo de la label stretch-end-image en mi repository.

Finalmente, hay un enfoque totalmente diferente que no implementé. Esto solo se aplicará al cambio de tamaño de las animaciones que crea usted mismo; no funcionará para la animation de rotation creada por el sistema en un cambio de orientación. Simplemente puede animar los límites de su vista de socket usted mismo, con un timer, en lugar de utilizar la animation UIKit. La Sesión 228 de WWDC 2012: Mejores prácticas para dominar el layout automático discute esto hacia el final. Sugiere usar un NSTimer pero creo que sería mejor usar un CADisplayLink . Si tomas este enfoque, las subvistas de la vista de complemento se animarán perfectamente. Es bastante más trabajo que usar animaciones UIKit, pero debe ser sencillo de implementar.

Modifiqué el código de Rob Mayoff de esta manera. ¿Esto te ayuda?

Los cambios importantes son en realidad el uso de CADisplayLink para actualizar el marco de plugView y agregué una subvista con restricciones para el plugView. También cambió cómo se agrega plugView y se actualiza su marco.

Verifica si esto funciona contigo. Debería poder replace este código en el proyecto sin problemas.

 #import "ViewController.h" @interface UIView (recursiveDescription) - (NSString *)recursiveDescription; @end @interface ViewController () @property (strong, nonatomic) IBOutlet NSLayoutConstraint *socketWidthConstraint; @property (strong, nonatomic) IBOutlet NSLayoutConstraint *socketHeightConstraint; @property (strong, nonatomic) IBOutlet UIView *socketView; @property (strong, nonatomic) IBOutlet UIImageView *plugView; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; UIView *extraView = [[UIView alloc] init]; extraView.translatesAutoresizingMaskIntoConstraints = NO; extraView.backgroundColor = [UIColor blackColor]; [self.plugView addSubview:extraView]; NSLayoutConstraint *c1 = [NSLayoutConstraint constraintWithItem:extraView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.plugView attribute:NSLayoutAttributeTop multiplier:1.0 constant:0]; NSLayoutConstraint *c2 = [NSLayoutConstraint constraintWithItem:extraView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.plugView attribute:NSLayoutAttributeLeading multiplier:1.0 constant:0]; NSLayoutConstraint *c3 = [NSLayoutConstraint constraintWithItem:extraView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.plugView attribute:NSLayoutAttributeWidth multiplier:0.5 constant:0]; NSLayoutConstraint *c4 = [NSLayoutConstraint constraintWithItem:extraView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.plugView attribute:NSLayoutAttributeHeight multiplier:0.5 constant:0]; [self.plugView addConstraints:@[c1, c2, c3, c4]]; } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkDidFire:)]; [link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; } - (void)displayLinkDidFire:(CADisplayLink *)link { CGSize size = [self.socketView.layer.presentationLayer frame].size; self.plugView.frame = CGRectMake(0, 0, size.width, size.height); [self.plugView layoutIfNeeded]; } - (IBAction)plugWasTapped:(id)sender { if (self.plugView.superview) { [self.plugView removeFromSuperview]; return; } CGSize size = [self.socketView.layer.presentationLayer frame].size; self.plugView.frame = CGRectMake(0, 0, size.width, size.height); [self.socketView addSubview:self.plugView]; } - (IBAction)bigButtonWasTapped:(id)sender { [UIView animateWithDuration:10 animations:^{ self.socketWidthConstraint.constant = 320; self.socketHeightConstraint.constant = 320; [self.view layoutIfNeeded]; }]; } - (IBAction)smallButtonWasTapped:(id)sender { [UIView animateWithDuration:5 animations:^{ self.socketWidthConstraint.constant = 160; self.socketHeightConstraint.constant = 160; [self.view layoutIfNeeded]; }]; } @end 

¿Por qué no creas que la vista de complemento siempre existe como vista secundaria de la vista de socket, pero establece hidden = YES ? O puede usar alpha = 0 . Luego, cuando quiera mostrarlo, simplemente configúrelo como oculto = hidden = NO o alpha = 1 .

De esta forma, su vista de plug-in siempre "aparecerá para el paseo" cuando esté animando la vista de zócalo.

Por cierto, su terminología es desorientadora para aquellos de nosotros que trabajamos con sockets TCP. ("Sockets? What?")