¿Cómo animo los cambios de restricción?

Estoy actualizando una aplicación antigua con una AdBannerView y cuando no hay anuncio, se desliza fuera de la pantalla. Cuando hay un anuncio, se desliza en la pantalla. Cosas básicas.

Viejo estilo, puse el marco en un bloque de animation. Nuevo estilo, tengo un IBOutlet a la restricción que determina la position Y, en este caso es la distancia desde la parte inferior de la supervisión, y modifico la constante.

 - (void)moveBannerOffScreen { [UIView animateWithDuration:5 animations:^{ _addBannerDistanceFromBottomConstraint.constant = -32; }]; bannerIsVisible = FALSE; } - (void)moveBannerOnScreen { [UIView animateWithDuration:5 animations:^{ _addBannerDistanceFromBottomConstraint.constant = 0; }]; bannerIsVisible = TRUE; } 

Y el banner se mueve, exactamente como se esperaba, pero no hay animation.

ACTUALIZACIÓN: volví a ver el video WWDC12 " Mejores prácticas para dominar el layout automático " que cubre la animation. Se analiza cómo actualizar las restricciones utilizando CoreAnimation .

introduzca la descripción de la imagen aquíintroduzca la descripción de la imagen aquí

Lo he intentado con el siguiente código, pero obtengo exactamente los mismos resultados.

 - (void)moveBannerOffScreen { _addBannerDistanceFromBottomConstraint.constant = -32; [UIView animateWithDuration:2 animations:^{ [self.view setNeedsLayout]; }]; bannerIsVisible = FALSE; } - (void)moveBannerOnScreen { _addBannerDistanceFromBottomConstraint.constant = 0; [UIView animateWithDuration:2 animations:^{ [self.view setNeedsLayout]; }]; bannerIsVisible = TRUE; } 

En una nota lateral, he comprobado varias veces y esto se está ejecutando en el hilo principal.

Dos notas importantes:

  1. layoutIfNeeded llamar a layoutIfNeeded dentro del bloque de animation. Apple recomienda que lo llames una vez antes del bloque de animation para asegurarte de que todas las operaciones de layout pendientes se hayan completado

  2. self.view llamarlo específicamente en la vista primaria (p. self.view ., self.view ), no en la vista secundaria que tiene las restricciones asociadas. Si lo hace, actualizará todas las vistas restringidas, incluida la animation de otras vistas que podrían estar restringidas a la vista en la que cambió la restricción (por ejemplo, la Vista B está adjunta a la parte inferior de la Vista A y acaba de cambiar la vista superior A y desea la Vista B para animar con ella)

Prueba esto:

C objective

 - (void)moveBannerOffScreen { [self.view layoutIfNeeded]; _addBannerDistanceFromBottomConstraint.constant = -32; [UIView animateWithDuration:5 animations:^{ [self.view layoutIfNeeded]; // Called on parent view }]; bannerIsVisible = FALSE; } - (void)moveBannerOnScreen { [self.view layoutIfNeeded]; _addBannerDistanceFromBottomConstraint.constant = 0; [UIView animateWithDuration:5 animations:^{ [self.view layoutIfNeeded]; // Called on parent view }]; bannerIsVisible = TRUE; } 

Swift 3

 _addBannerDistanceFromBottomConstraint.constant = 0 UIView.animate(withDuration: 5) { self.view.layoutIfNeeded() } 

Aprecio la respuesta proporcionada, pero creo que sería bueno llevarla un poco más lejos.

La animation básica de bloques de la documentation

 [containerView layoutIfNeeded]; // Ensures that all pending layout operations have been completed [UIView animateWithDuration:1.0 animations:^{ // Make all constraint changes here [containerView layoutIfNeeded]; // Forces the layout of the subtree animation block and then captures all of the frame changes }]; 

pero realmente este es un escenario muy simplist. ¿Qué pasa si quiero animar las restricciones de updateConstraints mediante el método updateConstraints ?

Un bloque de animation que llama a las subvenciones método updateConstraints

 [self.view layoutIfNeeded]; [self.subView setNeedsUpdateConstraints]; [self.subView updateConstraintsIfNeeded]; [UIView animateWithDuration:1.0f delay:0.0f options:UIViewAnimationOptionLayoutSubviews animations:^{ [self.view layoutIfNeeded]; } completion:nil]; 

El método updateConstraints se reemplaza en la subclass UIView y debe llamar super al final del método.

 - (void)updateConstraints { // Update some constraints [super updateConstraints]; } 

La guía AutoLayout deja mucho que desear, pero vale la pena leerla. Yo mismo estoy usando esto como parte de un UISwitch que alterna una subvista con un par de UITextField s con una animation de queuepso simple y sutil (0.2 segundos de duración). Las restricciones para la subvista se manejan en los methods updateConstraints de las subclasss UIView como se describió anteriormente.

En general, solo necesita actualizar las restricciones y llamar a layoutIfNeeded dentro del bloque de animation. Esto puede ser cambiar la propiedad .constant de un NSLayoutConstraint , agregar eliminar restricciones (iOS 7) o cambiar la propiedad .active de restricciones (iOS 8 y 9).

Código de muestra:

 [UIView animateWithDuration:0.3 animations:^{ // Move to right self.leadingConstraint.active = false; self.trailingConstraint.active = true; // Move to bottom self.topConstraint.active = false; self.bottomConstraint.active = true; // Make the animation happen [self.view setNeedsLayout]; [self.view layoutIfNeeded]; }]; 

Configuración de muestra:

Xcode Project por lo que muestra el proyecto de animación.

Controversia

Hay algunas preguntas sobre si la restricción debe modificarse antes del bloque de animation, o dentro de él (ver respuestas anteriores).

La siguiente es una conversación en Twitter entre Martin Pilkington, quien enseña iOS, y Ken Ferry, quien escribió Auto Layout. Ken explica que aunque las constantes cambiantes fuera del bloque de animation pueden funcionar actualmente , no es seguro y deberían cambiarse realmente dentro del bloque de animation. https://twitter.com/kongtomorrow/status/440627401018466305

Animación:

Proyecto de muestra

Aquí hay un proyecto simple que muestra cómo se puede animar una vista. Está utilizando Objective C y anima la vista al cambiar la propiedad .active de varias restricciones. https://github.com/shepting/SampleAutoLayoutAnimation

 // Step 1, update your constraint self.myOutletToConstraint.constant = 50; // New height (for example) // Step 2, trigger animation [UIView animateWithDuration:2.0 animations:^{ // Step 3, call layoutIfNeeded on your animated view's parent [self.view layoutIfNeeded]; }]; 

Storyboard, Code, Tips y algunos Gotchas

Las otras respuestas están bien, pero esto resalta algunas gotas bastante importantes de restricciones de animation usando un ejemplo reciente. Pasé por muchas variaciones antes de darme count de lo siguiente:

Haga las restricciones a las que desea orientar en Variables de class para tener una reference sólida. En Swift utilicé variables perezosas:

 lazy var centerYInflection:NSLayoutConstraint = { let temp = self.view.constraints.filter({ $0.firstItem is MNGStarRating }).filter ( { $0.secondItem is UIWebView }).filter({ $0.firstAttribute == .CenterY }).first return temp! }() 

Después de alguna experimentación noté que uno DEBE get la restricción de la vista ARRIBA (también conocida como supervisión) las dos vistas donde se define la restricción. En el siguiente ejemplo (tanto MNGStarRating como UIWebView son los dos types de elementos en los que estoy creando una restricción entre ellos, y son sub-vistas dentro de self.view).

Filtro de encadenamiento

Aprovecho el método de filter de Swift para separar la restricción deseada que servirá como punto de inflexión. También podría ser mucho más complicado, pero Filter hace un buen trabajo aquí.

Animando restricciones utilizando Swift

Nota Bene: este ejemplo es la solución de storyboard / code y asume que se han realizado restricciones pnetworkingeterminadas en el guión gráfico. Uno puede entonces animar los cambios usando el código.

Suponiendo que crea una propiedad para filtrar con criterios precisos y llegar a un punto de inflexión específico para su animation (por supuesto, también puede filtrar una matriz y recorrerlo si necesita múltiples restricciones):

 lazy var centerYInflection:NSLayoutConstraint = { let temp = self.view.constraints.filter({ $0.firstItem is MNGStarRating }).filter ( { $0.secondItem is UIWebView }).filter({ $0.firstAttribute == .CenterY }).first return temp! }() 

….

Algún time después…

 @IBAction func toggleRatingView (sender:AnyObject){ let aPointAboveScene = -(max(UIScreen.mainScreen().bounds.width,UIScreen.mainScreen().bounds.height) * 2.0) self.view.layoutIfNeeded() //Use any animation you want, I like the bounce in springVelocity... UIView.animateWithDuration(1.0, delay: 0.0, usingSpringWithDamping: 0.3, initialSpringVelocity: 0.75, options: [.CurveEaseOut], animations: { () -> Void in //I use the frames to determine if the view is on-screen if CGRectContainsRect(self.view.frame, self.ratingView.frame) { //in frame ~ animate away //I play a sound to give the animation some life self.centerYInflection.constant = aPointAboveScene self.centerYInflection.priority = UILayoutPriority(950) } else { //I play a different sound just to keep the user engaged //out of frame ~ animate into scene self.centerYInflection.constant = 0 self.centerYInflection.priority = UILayoutPriority(950) self.view.setNeedsLayout() self.view.layoutIfNeeded() }) { (success) -> Void in //do something else } } } 

Los muchos giros equivocados

Estas notas son realmente un set de consejos que escribí para mí. Hice todo lo que se podía y personalmente y dolorosamente. Esperemos que esta guía pueda evitar a otros.

  1. Cuidado con zPositioning. A veces, cuando aparentemente no sucede nada, debe esconder algunas de las otras vistas o usar el depurador de vistas para ubicar su vista animada. Incluso he encontrado casos en los que se perdió un atributo de time de ejecución definido por el usuario en el xml de un guión gráfico y se llevó a la vista animada que estaba cubierta (mientras trabajaba).

  2. Siempre tómese un minuto para leer la documentation (nueva y antigua), Ayuda rápida y encabezados. Apple sigue realizando muchos cambios para gestionar mejor las restricciones de AutoLayout (consulte las vistas de la stack). O al less el libro de cocina AutoLayout . Tenga en count que a veces las mejores soluciones se encuentran en los documentos / videos más antiguos.

  3. Juega con los valores en la animation y considera usar otras variantes animateWithDuration.

  4. No utilice los valores de layout específicos de código rígido como criterio para determinar los cambios en otras constantes, sino que use valores que le permitan determinar la location de la vista. CGRectContainsRect es un ejemplo

  5. Si es necesario, no dude en utilizar los márgenes de layout asociados a una vista que participe en la definición de restricción, let viewMargins = self.webview.layoutMarginsGuide : está en ejemplo
  6. No haga el trabajo que no tiene que hacer, todas las vistas con restricciones en el guión gráfico tienen restricciones asociadas a la propiedad self.viewName.constraints
  7. Cambie sus prioridades para cualquier restricción a less de 1000. Puse el mío en 250 (bajo) o 750 (alto) en el guión gráfico; (si intenta cambiar una prioridad de 1000 a cualquier cosa en el código, la aplicación se bloqueará porque se requiere 1000)
  8. Considere no tratar inmediatamente de usar activateConstraints y desactivarConstraints (tienen su lugar, pero cuando acaba de aprender o si está usando un storyboard usando estos probablemente significa que hace demasiado; sin embargo, tienen un lugar como se ve a continuación)
  9. Considere no usar addConstraints / removeConstraints a less que realmente esté agregando una nueva restricción en el código. Descubrí que la mayoría de las veces layout las vistas en el guión gráfico con las restricciones deseadas (colocando la vista fuera de la pantalla), luego en el código, animo las restricciones creadas previamente en el guión gráfico para mover la vista.
  10. Pasé mucho time perdido construyendo restricciones con la nueva class y subclasss NSAnchorLayout. Estos funcionan bien, pero me tomó un time darse count de que todas las restricciones que necesitaba ya existían en el guión gráfico. Si construye restricciones en el código, entonces seguramente use este método para agregar sus restricciones:

Muestra rápida de soluciones para EVITAR al usar Storyboards

 private var _nc:[NSLayoutConstraint] = [] lazy var newConstraints:[NSLayoutConstraint] = { if !(self._nc.isEmpty) { return self._nc } let viewMargins = self.webview.layoutMarginsGuide let minimumScreenWidth = min(UIScreen.mainScreen().bounds.width,UIScreen.mainScreen().bounds.height) let centerY = self.ratingView.centerYAnchor.constraintEqualToAnchor(self.webview.centerYAnchor) centerY.constant = -1000.0 centerY.priority = (950) let centerX = self.ratingView.centerXAnchor.constraintEqualToAnchor(self.webview.centerXAnchor) centerX.priority = (950) if let buttonConstraints = self.originalRatingViewConstraints?.filter({ ($0.firstItem is UIButton || $0.secondItem is UIButton ) }) { self._nc.appendContentsOf(buttonConstraints) } self._nc.append( centerY) self._nc.append( centerX) self._nc.append (self.ratingView.leadingAnchor.constraintEqualToAnchor(viewMargins.leadingAnchor, constant: 10.0)) self._nc.append (self.ratingView.trailingAnchor.constraintEqualToAnchor(viewMargins.trailingAnchor, constant: 10.0)) self._nc.append (self.ratingView.widthAnchor.constraintEqualToConstant((minimumScreenWidth - 20.0))) self._nc.append (self.ratingView.heightAnchor.constraintEqualToConstant(200.0)) return self._nc }() 

Si olvida uno de estos consejos o los más simples, como dónde agregar el layoutIfNeeded, lo más probable es que no ocurra nada: en ese caso, puede tener una solución media horneada como esta:

NB – Tómese un momento para leer la sección de AutoLayout a continuación y la guía original. Existe una forma de utilizar estas técnicas para complementar sus Animadores dynamics.

 UIView.animateWithDuration(1.0, delay: 0.0, usingSpringWithDamping: 0.3, initialSpringVelocity: 1.0, options: [.CurveEaseOut], animations: { () -> Void in // if self.starTopInflectionPoint.constant < 0 { //-3000 //offscreen self.starTopInflectionPoint.constant = self.navigationController?.navigationBar.bounds.height ?? 0 self.changeConstraintPriority([self.starTopInflectionPoint], value: UILayoutPriority(950), forView: self.ratingView) } else { self.starTopInflectionPoint.constant = -3000 self.changeConstraintPriority([self.starTopInflectionPoint], value: UILayoutPriority(950), forView: self.ratingView) } }) { (success) -> Void in //do something else } } 

Fragmento de la guía de AutoLayout (tenga en count que el segundo fragment es para usar OS X). BTW: esto ya no está en la guía actual por lo que puedo ver. Las técnicas preferidas continúan evolucionando.

Animación de cambios realizados por layout automático

Si necesita un control total sobre la animation de los cambios realizados por el Diseño automático, debe hacer que los cambios de la restricción sean programáticos. El concepto básico es el mismo para iOS y OS X, pero hay algunas diferencias menores.

En una aplicación de iOS, su código se parecería a lo siguiente:

 [containerView layoutIfNeeded]; // Ensures that all pending layout operations have been completed [UIView animateWithDuration:1.0 animations:^{ // Make all constraint changes here [containerView layoutIfNeeded]; // Forces the layout of the subtree animation block and then captures all of the frame changes }]; 

En OS X, use el siguiente código cuando utilice animaciones respaldadas por capas:

 [containterView layoutSubtreeIfNeeded]; [NSAnimationContext runAnimationGroup:^(NSAnimationContext *context) { [context setAllowsImplicitAnimation: YES]; // Make all constraint changes here [containerView layoutSubtreeIfNeeded]; }]; 

Cuando no está utilizando animaciones respaldadas por capas, debe animar la constante utilizando el animador de la restricción:

 [[constraint animator] setConstant:42]; 

Para aquellos que aprenden mejor visualmente vea este video temprano de Apple .

Presta mucha atención

A menudo en la documentation hay pequeñas notas o fragments de código que llevan a ideas más grandes. Por ejemplo, adjuntar restricciones de layout automático a los animadores dynamics es una gran idea.

Buena suerte y que la fuerza esté contigo.

Intentaba animar las restricciones y no fue fácil encontrar una buena explicación.

Lo que dicen otras respuestas es totalmente cierto: debe llamar a [self.view layoutIfNeeded]; Dentro de animateWithDuration: animations: Sin embargo, el otro punto importante es tener pointers para cada NSLayoutConstraint que desee animar.

Creé un ejemplo en GitHub .

Solución rápida:

 yourConstraint.constant = 50 UIView.animateWithDuration(1, animations: yourView.layoutIfNeeded) 

Solución de trabajo 100% Swift 3.1

He leído todas las respuestas y quiero compartir el código y la jerarquía de las líneas que he usado en todas mis aplicaciones para animarlas correctamente. Algunas soluciones aquí no funcionan, deberías consultarlas en dispositivos más lentos, por ejemplo, el iPhone 5 en este momento.

 self.view.layoutIfNeeded() // Force lays of all subviews on root view UIView.animate(withDuration: 0.5) { [weak self] in // allowing to ARC to deallocate it properly self?.tbConstraint.constant = 158 // my constraint constant change self?.view.layoutIfNeeded() // Force lays of all subviews on root view again. } 

Hay un artículo sobre esto: http://weblog.invasivecode.com/post/42362079291/auto-layout-and-core-animation-auto-layout-was

En el cual codificó de la siguiente manera:

 - (void)handleTapFrom:(UIGestureRecognizer *)gesture { if (_isVisible) { _isVisible = NO; self.topConstraint.constant = -44.; // 1 [self.navbar setNeedsUpdateConstraints]; // 2 [UIView animateWithDuration:.3 animations:^{ [self.navbar layoutIfNeeded]; // 3 }]; } else { _isVisible = YES; self.topConstraint.constant = 0.; [self.navbar setNeedsUpdateConstraints]; [UIView animateWithDuration:.3 animations:^{ [self.navbar layoutIfNeeded]; }]; } } 

Espero eso ayude.

Trabajo y solución probada para Swift 3 con Xcode 8.3.3:

 self.view.layoutIfNeeded() self.calendarViewHeight.constant = 56.0 UIView.animate(withDuration: 0.5, delay: 0.0, options: UIViewAnimationOptions.curveEaseIn, animations: { self.view.layoutIfNeeded() }, completion: nil) 

Solo tenga en count que self.calendarViewHeight es una restricción referida a un customView (CalendarView). Llamé a .layoutIfNeeded () en self.view y NO en self.calendarView

Espero que esto ayude.

En el context de la animation de restricciones, me gustaría mencionar una situación específica en la que animé una restricción inmediatamente dentro de una notificación con keyboard_opertado.

Restricción definió un espacio superior desde un campo de text a la parte superior del contenedor. Al abrir el keyboard, acabo de dividir la constante por 2.

No pude lograr una animation de limitación constante con coinheritance directamente dentro de la notificación del keyboard. Aproximadamente la mitad de las veces, la vista simplemente saltaría a su nueva position, sin animation.

Se me ocurrió que podría haber un layout adicional como resultado de la apertura del keyboard. Al agregar un simple bloque dispatch_after con un retraso de 10ms, la animation se ejecutaba cada vez, sin saltos.