Pregunta ¿Cómo puedo animar los cambios de restricción?
Estoy actualizando una aplicación vieja con un AdBannerView
y cuando no hay 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
a la restricción que determina la posición Y, en este caso es la distancia desde la parte inferior de la supervista, y modificar 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.
ACTUALIZAR: 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
.

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.
832
2017-09-27 13:23
origen
Respuestas:
Dos notas importantes:
Tienes que llamar 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
Debe llamarlo específicamente en el vista de los padres (p.ej. self.view
), no la vista secundaria que tiene las restricciones asociadas. Al hacerlo, se actualizará todas vistas restringidas, incluida la animación de otras vistas que podrían estar restringidas a la vista que cambió la restricción de (por ejemplo, Vista B está adjunta a la parte inferior de la Vista A y acaba de cambiar la compensación superior de la Vista A y desea que la Vista B la anime)
Prueba esto:
C objetivo
- (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()
}
1505
2017-09-30 19:00
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é ocurre si quiero animar las restricciones de la subvista a través del updateConstraints
¿método?
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 leerlo. 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 largo). Las restricciones para la subvista se manejan en los métodos de actualización de subclases de UIView como se describió anteriormente.
96
2018-01-22 00:35
Generalmente, solo necesitas actualizar las restricciones y llamar layoutIfNeeded
dentro del bloque de animación. Esto puede ser cambiar el .constant
propiedad de un NSLayoutConstraint
, agregar restricciones de eliminación (iOS 7) o cambiar el .active
propiedad 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:

Controversia
Hay algunas preguntas sobre si la restricción debería cambiarse antes de el bloque de animación, o dentro (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 cambie las constantes fuera del bloque de animación puede actualmente trabajo, no es seguro y realmente deberían ser cambios dentro el 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á usando Objective C y anima la vista cambiando el .active
propiedad de varias restricciones.
https://github.com/shepting/SampleAutoLayoutAnimation
58
2017-11-13 00:33
// 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];
}];
32
2017-12-23 14:29
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 ENCIMA (aka 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 asume
uno ha hecho restricciones predeterminadas en el guión gráfico. Uno puede entonces
anima 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.
Tenga cuidado con zPositioning. A veces cuando aparentemente no hay nada
pasando, debería ocultar algunas de las otras vistas o usar la vista
depurador para ubicar su vista animada. Incluso encontré casos donde un tiempo de ejecución definido por el usuario
El atributo se perdió en el xml de un guión gráfico y condujo a la animación
vista cubierta (mientras trabaja).
Siempre tómese un minuto para leer la documentación (nueva y antigua), Quick
Ayuda y encabezados Apple sigue haciendo muchos cambios para mejorar
gestionar las restricciones de AutoLayout (ver vistas de la pila). O al menos el AutoLayout Cookbook. Tenga en cuenta que a veces las mejores soluciones se encuentran en la documentación / videos anteriores.
Juega con los valores de la animación y considera usar
otras variantes de animateWithDuration.
No codificar los valores 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 uno
ejemplo
- Si es necesario, no dude en utilizar los márgenes de diseño asociados con
una vista que participa en la definición de restricción
let viewMargins = self.webview.layoutMarginsGuide
: está en el ejemplo
- No haga el trabajo que no tiene que hacer, todas las vistas con restricciones en el
el guión gráfico tiene restricciones asociadas a la propiedad
self.viewName.constraints
- Cambie sus prioridades para cualquier restricción a menos de 1000. Configuré
mina a 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)
- Considere no intentar de inmediato el uso de activateConstraints y
desactiva las Restricciones (tienen su lugar, pero cuando estás aprendiendo o si estás usando un guión gráfico, usarlas probablemente significa que estás haciendo demasiado ~ aunque tienen un lugar como se ve a continuación)
- Considere no usar addConstraints / removeConstraints a menos que sea
realmente agregando una nueva restricción en el código. Encontré que la mayoría de las veces
diseñar las vistas en el guión gráfico con las restricciones deseadas (
la vista fuera de la pantalla), luego en el código, yo animo las restricciones previamente creadas en el guión gráfico para mover la vista.
- Pasé mucho tiempo perdido acumulando restricciones con el nuevo
Clase y subclases NSAnchorLayout. Estos funcionan bien, pero
me tomó un tiempo darme cuenta de que todas las limitaciones que necesitaba
ya existía en el guión gráfico. Si construyes restricciones en el código
luego, sin duda, 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 usar estas técnicas para complementar
tus animadores dinámicos
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 programació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 revisa esto temprano video 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 dinámicos es una gran idea.
Buena suerte y que la fuerza te acompañe.
10
2017-12-11 01:49
Solución Swift 4
UIView.animate
Tres simples pasos:
Cambiar las restricciones, p. Ej .:
heightAnchor.constant = 50
Dile a los que contienen view
que su diseño está sucio y que el diseño automático debería volver a calcular el diseño:
self.view.setNeedsLayout()
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 a las que desea llamar self.view.layoutIfNeeded()
para asegurarse de que el punto de partida de la animación sea del estado con las antiguas restricciones aplicadas (en caso de que haya otros cambios de restricciones que no deberían 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()
Ya que animator
es una encapsulación de la animación, podemos seguir haciendo referencia a ella y llamarla más tarde. Sin embargo, dado que en el bloque de animación solo le decimos a la función de autodiseño que recalcule los cuadros, tenemos que cambiar las restricciones antes de llamar 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.
7
2018-02-16 11:09
Solución Swift:
yourConstraint.constant = 50
UIView.animateWithDuration(1, animations: yourView.layoutIfNeeded)
6
2017-08-11 08:42
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, debe verificarlas en dispositivos más lentos como 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.
}
5
2018-06-12 08:41
Estaba intentando animar Restricciones y no fue realmente fácil encontrar una buena explicación.
Lo que otras respuestas dicen es totalmente cierto: debes llamar [self.view layoutIfNeeded];
dentro animateWithDuration: animations:
. Sin embargo, el otro punto importante es tener punteros para cada NSLayoutConstraint
quieres animar
Creé un ejemplo en GitHub.
4
2017-11-25 08:24
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.
3
2017-07-19 12:17