¿Cómo puedo animar los cambios de restricción?

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

Estilo antiguo, configuro el marco en un bloque de animación. Nuevo estilo, tengo un IBOutlet para la restricción que determina la posición Y, en este caso es la distancia desde la parte inferior de la supervista, 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 sin animación.

ACTUALIZACIÓN: volví a ver el video de WWDC12 ” Mejores prácticas para dominar el diseño automático ” que cubre la animación. Discute cómo actualizar las restricciones usando CoreAnimation .

enter image description hereenter image description here

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 en numerosas ocasiones y esto se está ejecutando en el hilo principal.

Dos notas importantes:

  1. layoutIfNeeded llamar a layoutIfNeeded dentro del bloque de animación. Apple realmente recomienda que lo llames una vez antes del bloque de animación para asegurarte de que todas las operaciones de diseño pendientes se hayan completado

  2. self.view llamarlo específicamente en la vista principal (por ejemplo, self.view ), no en la vista secundaria que tiene las restricciones asociadas. Al hacerlo, se actualizarán todas las vistas restringidas, incluida la animación de otras vistas que podrían estar restringidas a la vista que usted modificó (por ejemplo, la Vista B está adjunta a la parte inferior de la Vista A y usted acaba de cambiar la compensación superior de la Vista A y desea ver B animar con eso)

Prueba esto:

C objective

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

Swift 3

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

Agradezco la respuesta brindada, pero creo que sería bueno llevarlo un poco más allá.

La animación de bloque básica de la documentación

 [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 simplista. ¿Qué pasa si quiero animar las restricciones de subvista a través del método updateConstraints ?

Un bloque de animación que llama al método updateSonscriptions de las subvistas

 [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 subclase UIView y debe llamar a super al final del método.

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

La Guía de 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 animación de colapso simple y sutil (0.2 segundos de duración). Las restricciones para la subvista se manejan en los métodos de actualización de subclases de UIView como se describió anteriormente.

Generalmente, solo necesita actualizar las restricciones y llamar a layoutIfNeeded dentro del bloque de animación. Esto puede cambiar la propiedad .constant de NSLayoutConstraint , agregar restricciones de eliminación (iOS 7) o cambiar la propiedad .active de las 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 así que muestra un proyecto de animación.

Controversia

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

La siguiente es una conversación de Twitter entre Martin Pilkington, que enseña iOS, y Ken Ferry, quien escribió Auto Layout. Ken explica que aunque el cambio de constantes fuera del bloque de animación actualmente puede funcionar, no es seguro y realmente deberían cambiar dentro del bloque de animación. 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 cambiando 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 algunas Gotchas

Las otras respuestas están bien, pero esta resalta algunas capturas bastante importantes de restricciones de animación usando un ejemplo reciente. Pasé por muchas variaciones antes de darme cuenta de lo siguiente:

Establezca las restricciones que desea apuntar en variables de clase para mantener una referencia fuerte. En Swift usé 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 un poco de experimentación, noté que uno DEBE obtener la restricción de la vista ARRIBA (también conocida como la supervista) las dos vistas donde se define la restricción. En el ejemplo a continuación (tanto MNGStarRating como UIWebView son los dos tipos de elementos en los que estoy creando una restricción, y son subvistas dentro de self.view).

Encadenamiento del filtro

Aprovecho el método de filtro 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 el filtro hace un buen trabajo aquí.

Animar restricciones usando Swift

Nota Bene – Este ejemplo es la solución de guión gráfico / código y supone que uno ha hecho restricciones predeterminadas en el guión gráfico. Uno puede entonces animar los cambios usando código.

Asumiendo que crea una propiedad para filtrar con criterios precisos y llega a un punto de inflexión específico para su animación (por supuesto, también puede filtrar por una matriz y recorrer 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 tiempo 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 incorrectos

Estas notas son realmente un conjunto de consejos que escribí para mí. Hice todo lo que no debo hacer personalmente y dolorosamente. Con suerte, esta guía puede ahorrarles a los demás.

  1. Tenga cuidado con zPositioning. A veces, cuando aparentemente no ocurre nada, debe ocultar algunas de las otras vistas o usar el depurador de vista para ubicar su vista animada. Incluso encontré casos en los que se perdió un atributo de tiempo de ejecución definido por el usuario en el xml de un guión gráfico y se ha cubierto la vista animada (mientras se trabaja).

  2. Siempre tómese un minuto para leer la documentación (nueva y antigua), Ayuda rápida y encabezados. Apple sigue haciendo muchos cambios para administrar mejor las restricciones de AutoLayout (vea las vistas de la stack). O al menos el AutoLayout Cookbook . Tenga en cuenta que a veces las mejores soluciones se encuentran en la documentación / videos anteriores.

  3. Juega con los valores de la animación y considera usar otras variantes de animateWithDuration.

  4. No codifique códigos de diseño específicos como criterios para determinar cambios en otras constantes, en su lugar use valores que le permitan determinar la ubicación de la vista. CGRectContainsRect es un ejemplo

  5. Si es necesario, no dude en utilizar los márgenes de diseño asociados a una vista que participe en la definición de restricción let viewMargins = self.webview.layoutMarginsGuide : is on example
  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 menos de 1000. Establecí 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 intentar usar inmediatamente las restricciones de activación y desactivar las restricciones (tienen su lugar, pero cuando acaba de aprender o si está usando un guión gráfico, usarlas probablemente signifique que usted hace demasiado ~ aunque tienen un lugar como se ve a continuación)
  9. Considere no usar addConstraints / removeConstraints a menos que realmente esté agregando una nueva restricción en el código. Descubrí que la mayoría de las veces distribuyo 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 previamente creadas en el guión gráfico para mover la vista.
  10. Pasé mucho tiempo perdido acumulando restricciones con la nueva clase y subclases de NSAnchorLayout. Estos funcionan bien, pero me tomó un tiempo darme cuenta de que todas las limitaciones que necesitaba ya existían en el guión gráfico. Si crea restricciones en el código, seguramente use este método para agregar sus restricciones:

Muestra rápida de soluciones para EVITAR al usar guiones gráficos

 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 diseño, si es necesario, lo más probable es que no ocurra nada: en ese caso, puede que tenga una solución medio horneada como esta:

NB – Tómese un momento para leer la sección de AutoLayout a continuación y la guía original. Hay una manera 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 cuenta que el segundo fragmento es para usar OS X). Por cierto, 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 diseño automático

Si necesita un control total sobre la animación de los cambios realizados por Diseño automático, debe realizar sus cambios de restricción mediante progtwigción. 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 vería como el 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 use 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 capa, debe animar la constante usando el animador de la restricción:

 [[constraint animator] setConstant:42]; 

Para aquellos que aprenden mejor visualmente echa un vistazo a este video temprano de Apple .

Presta mucha atención

A menudo, en la documentación, hay pequeñas notas o fragmentos de código que conducen a ideas más grandes. Por ejemplo, adjuntar restricciones de diseño automático a animadores dynamics es una gran idea.

Buena suerte y que la fuerza te acompañe.

Solución Swift 4

UIView.animate

Tres simples pasos:

  1. Cambiar las restricciones, por ejemplo:

     heightAnchor.constant = 50 
  2. Indica a la view que contiene que su diseño está sucio y que el diseño automático debe volver a calcular el diseño:

     self.view.setNeedsLayout() 
  3. En el bloque de animación, dígale al diseño que recalcule el diseño, lo que es equivalente a establecer los marcos directamente (en este caso, el ajuste automático establecerá los marcos):

     UIView.animate(withDuration: 0.5) { self.view.layoutIfNeeded() } 

Complete el ejemplo más simple:

 heightAnchor.constant = 50 self.view.setNeedsLayout() UIView.animate(withDuration: 0.5) { self.view.layoutIfNeeded() } 

Nota al margen

Hay un décimo paso opcional: antes de cambiar las restricciones, puede llamar a self.view.layoutIfNeeded() para asegurarse de que el punto de partida de la animación es del estado con las antiguas restricciones aplicadas (en caso de que haya cambios en otras restricciones) eso no debería incluirse en la animación):

 otherConstraint.constant = 30 // this will make sure that otherConstraint won't be animated but will take effect immediately self.view.layoutIfNeeded() heightAnchor.constant = 50 self.view.setNeedsLayout() UIView.animate(withDuration: 0.5) { self.view.layoutIfNeeded() } 

UIViewPropertyAnimator

Dado que con iOS 10 tenemos un nuevo mecanismo de animación: UIViewPropertyAnimator , debemos saber que, básicamente, se aplica el mismo mecanismo. Los pasos son básicamente los mismos:

 heightAnchor.constant = 50 self.view.setNeedsLayout() let animator = UIViewPropertyAnimator(duration: 0.5, timingParameters: UICubicTimingParameters(animationCurve: .linear)) animator.addAnimations { self.view.layoutIfNeeded() } animator.startAnimation() 

Dado que el animator es una encapsulación de la animación, podemos mantener la referencia y llamarla más tarde. Sin embargo, dado que en el bloque de animación simplemente le decimos a la función autolayout que vuelva a calcular los fotogtwigs, tenemos que cambiar las restricciones antes de llamar a startAnimation . Por lo tanto, algo como esto es posible:

 // prepare the animator first and keep a reference to it let animator = UIViewPropertyAnimator(duration: 0.5, timingParameters: UICubicTimingParameters(animationCurve: .linear)) animator.addAnimations { self.view.layoutIfNeeded() } // at some other point in time we change the constraints and call the animator heightAnchor.constant = 50 self.view.setNeedsLayout() animator.startAnimation() 

El orden de las restricciones cambiantes y el inicio de un animador es importante: si simplemente cambiamos las restricciones y dejamos nuestro animador para un punto posterior, el próximo ciclo de redibujado puede invocar el recálculo de la autoimpresión y el cambio no se animará.

Además, recuerde que un único animador no es reutilizable: una vez que lo ejecuta, no puede “volver a ejecutarlo”. Así que supongo que no hay realmente una buena razón para mantener al animador cerca, a menos que lo usemos para controlar una animación interactiva.

Solución Swift:

 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 utilizado en todas mis aplicaciones para animarlas correctamente. Algunas soluciones aquí no funcionan; debería verificarlas en dispositivos más lentos, por ejemplo 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. } 

Estaba intentando animar Restricciones y no fue realmente fácil encontrar una buena explicación.

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

Creé un ejemplo en GitHub .

Solución de trabajo y recié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 cuenta que self.calendarViewHeight es una restricción referida a una vista personalizada (Vista de calendario). Llamé a .layoutIfNeeded () en self.view y NO en self.calendarView

Espero que esto ayude.

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

En el cual, él codificó de esta 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.

En el contexto de la animación de restricciones, me gustaría mencionar una situación específica en la que animé una restricción inmediatamente dentro de una notificación keyboard_opened.

Restricción definió un espacio superior desde un campo de texto hasta la parte superior del contenedor. Al abrir el teclado, simplemente divido la constante por 2.

No pude lograr una animación de restricción suave uniforme directamente dentro de la notificación del teclado. La mitad de las veces la vista simplemente salta a su nueva posición, sin animación.

Se me ocurrió que podría haber algún tipo de disposición adicional como resultado de la apertura del teclado. Agregar un simple dispatch_after bloque con un retraso de 10ms hizo que la animación se ejecutara cada vez, sin saltos.