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

959

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

Estilo antiguo, puse el marco en un bloque de animación. Nuevo estilo, tengo una IBOutletrestricción de diseño automático que determina la Yposición, en este caso, es la distancia desde la parte inferior de la supervista y modifica 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 animación.


ACTUALIZACIÓN: volví a ver la charla de WWDC 12 Mejores prácticas para dominar el diseño automático que cubre la animación. Discute cómo actualizar 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 al margen, lo he comprobado varias veces y esto se está ejecutando en el hilo principal .

DBD
fuente
11
Nunca antes había visto tantos votos ofrecidos por una pregunta y respuesta sobre un error tipográfico en SO
abbood el
3
Si hay un error tipográfico en la respuesta, debe editar la respuesta. Por eso son editables.
i_am_jorf
@jeffamaphone: sería más útil si señalaras el error tipográfico para saber dónde estaba el error. Puede editar la respuesta usted mismo y corregir el error tipográfico salvando a todos los demás nuestra diatriba. Acabo de editarlo para eliminar la constante del bloque de animación, si a eso se refería.
DBD
1
No sé cuál es el error tipográfico. Estaba respondiendo a los comentarios anteriores.
i_am_jorf
99
Entonces el error tipográfico es la pregunta. Estúpidamente estaba escribiendo "setNeedsLayout" en lugar de "layoutIfNeeded". Se muestra claramente en mi pregunta cuando corto y pego mi código con el error y las capturas de pantalla con el comando correcto. Sin embargo, no podía notarlo hasta que alguien lo señaló.
DBD

Respuestas:

1697

Dos notas importantes:

  1. Debe llamar layoutIfNeededdentro del bloque de animación. Apple realmente recomienda que lo llame una vez antes del bloque de animación para asegurarse de que se hayan completado todas las operaciones de diseño pendientes

  2. Debe llamarlo específicamente en la vista principal (p self.view. Ej. ), 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 de la que cambió la restricción (por ejemplo, la Vista B se adjunta a la parte inferior de la Vista A y acaba de cambiar el desplazamiento superior de la Vista A y desea la Vista B animar con eso)

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()
}
g3rv4
fuente
200
Sabes qué ... tu respuesta funciona. El WWDC funciona ... mi visión falla. Por alguna razón, me llevó una semana darme cuenta de que estaba llamando en setNeedsLayoutlugar de layoutIfNeeded. Estoy un poco horrorizado por la cantidad de horas que pasé sin darme cuenta de que solo escribí el nombre del método incorrecto.
DBD
23
La solución funciona pero no necesita cambiar la constante de restricción dentro del bloque de animación. Está totalmente bien establecer la restricción una vez antes de comenzar la animación. Deberías editar tu respuesta.
Ortwin Gentz
74
Inicialmente, esto no funcionó para mí y luego me di cuenta de que necesita llamar a layoutIfNeeded en la vista PADRE, no a la vista a la que se aplican las restricciones.
Oliver Pearmain
20
El uso de layoutIfNeeded animará todas las actualizaciones de subvista, no solo el cambio de restricción. ¿Cómo animar solo el cambio de restricción?
ngb
11
"Apple realmente recomienda que lo llames una vez antes del bloque de animación para asegurarte de que se hayan completado todas las operaciones de diseño pendientes", gracias, nunca pensé en eso, pero tiene sentido.
Rick van der Linde
109

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

La animación básica de bloques 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é sucede si quiero animar restricciones de subvista a través del updateConstraintsmétodo?

Un bloque de animación que llama al método updateConstraints de 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 anula 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 diseño automático deja mucho que desear, pero vale la pena leerla. Yo mismo estoy usando esto como parte de una UISwitchque alterna una subvista con un par de UITextFields 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 updateConstraints de las subclases de UIView como se describió anteriormente.

Cameron Lowell Palmer
fuente
Al invocar todos los métodos anteriores self.view(y no en una subvista de esa vista), updateConstraintsIfNeededno es necesario llamar (porque setNeedsLayouttambién desencadena updateConstraintsesa vista). Podría ser trivial para la mayoría, pero no fue para mí hasta ahora;)
anneblue
Es difícil comentar sin conocer la interacción completa de Autolayout y el diseño de resortes y puntales en su caso particular. Estoy hablando por completo de un escenario de autolavado puro. Me gustaría saber qué está sucediendo exactamente en su método layoutSubviews.
Cameron Lowell Palmer
traduceAutoresizingMaskIntoConstraints = NO; Si desea una reproducción automática pura, definitivamente debe deshabilitar esto.
Cameron Lowell Palmer
No he visto eso, pero realmente no puedo hablar sobre el problema sin algún código. ¿Tal vez podrías simular un TableView y pegarlo en GitHub?
Cameron Lowell Palmer
Lo siento, no uso gitHub :(
anneblue
74

En general, solo necesita actualizar las restricciones y llamar layoutIfNeededdentro del bloque de animación. Esto puede ser cambiar la .constantpropiedad de un NSLayoutConstraint, agregar restricciones de eliminación (iOS 7) o cambiar la .activepropiedad 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:

Proyecto Xcode, 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 en Twitter entre Martin Pilkington, que enseña iOS, y Ken Ferry, quien escribió Auto Layout. Ken explica que aunque cambiar constantes fuera del bloque de animación puede funcionar actualmente , no es seguro y realmente deberían cambiarse 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 el Objetivo C y anima la vista cambiando la .activepropiedad de varias restricciones. https://github.com/shepting/SampleAutoLayoutAnimation

Steven Hepting
fuente
+1 para mostrar un ejemplo usando la nueva bandera activa. Además, cambiar la restricción fuera del bloque de animación siempre me pareció un truco
Korey Hinton el
Planteé un problema en github porque esta solución no funciona para mover la vista a donde estaba. Creo que el cambio activo no es la solución correcta y en su lugar debería cambiar la prioridad. Establezca la parte superior en 750 y la parte inferior en 250, luego en el código alterne entre UILayoutPriorityDefaultHigh y UILayoutPriorityDefaultLow.
malhal
Otra razón para actualizar dentro del bloque es que aísla los cambios del código de llamada que también pueden llamar a layoutIfNeeded. (y gracias por el enlace de Twitter)
Chris Conover
1
Gran respuesta. Especialmente porque mencionas la conversación de Martin y Ken sobre esto.
Jona
Estoy confundido sobre la forma de actualizar las restricciones y escribí esto . ¿Puedes por favor echar un vistazo?
Miel
36
// 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];
}];
John Erck
fuente
29

Solución Swift 4

UIView.animate

Tres simples pasos:

  1. Cambiar las restricciones, por ejemplo:

    heightAnchor.constant = 50
  2. Indique al contenedor viewque su diseño está sucio y que la distribución automática debe volver a calcular el diseño:

    self.view.setNeedsLayout()
  3. En el bloque de animación, dígale al diseño que vuelva a calcular el diseño, que es equivalente a configurar los cuadros directamente (en este caso, la distribución automática establecerá los cuadros):

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

Completo ejemplo más simple:

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

Nota al margen

Hay un paso 0 opcional: antes de cambiar las restricciones, es posible que desee llamar self.view.layoutIfNeeded()para asegurarse de que el punto de partida para la animación sea del estado con las restricciones anteriores aplicadas (en caso de que hubiera 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, deberíamos 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 animatores una encapsulación de la animación, podemos mantener referencia a ella y llamarla más tarde. Sin embargo, dado que en el bloque de animación simplemente le decimos al autolayout 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 cambiar las restricciones y comenzar un animador es importante: si solo cambiamos las restricciones y dejamos nuestro animador para algún punto posterior, el siguiente ciclo de redibujo puede invocar el recálculo de la distribución automática y el cambio no se animará.

Además, recuerde que un solo animador no es reutilizable: una vez que lo ejecuta, no puede "volver a ejecutarlo". Así que supongo que no hay una buena razón para mantener el animador, a menos que lo usemos para controlar una animación interactiva.

Milan Nosáľ
fuente
👍 funciona muy bien en swift 5 también
spnkr
layoutIfNeeded () es la clave
Medhi
15

Storyboard, Código, Consejos y algunas Gotchas

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

Convierta 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 experimentar un poco, noté que 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 entre los que estoy creando una restricción, y son subvistas dentro de self.view).

Encadenamiento de 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 establecido restricciones predeterminadas en el guión gráfico. Entonces uno puede animar los cambios usando el código.

Suponiendo que cree una propiedad para filtrar con criterios precisos y llegue a un punto de inflexión específico para su animación (por supuesto, también puede filtrar una matriz y recorrerla 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 equivocados

Estas notas son realmente un conjunto de consejos que escribí para mí. Hice todo lo que no debo hacer personal y dolorosamente. Esperemos que esta guía pueda ahorrarle a otros.

  1. Tenga cuidado con zPosposition. A veces, cuando aparentemente no ocurre nada, debe ocultar algunas de las otras vistas o usar el depurador de vistas para ubicar su vista animada. Incluso he encontrado casos en los que un Atributo de tiempo de ejecución definido por el usuario se perdió en el xml de un guión gráfico y condujo a que se cubriera la vista animada (mientras trabajaba).

  2. Siempre tome un minuto para leer la documentación (nueva y antigua), la Ayuda rápida y los encabezados. Apple sigue haciendo muchos cambios para gestionar mejor las restricciones de AutoLayout (ver vistas de pila). 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 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. CGRectContainsRectes un ejemplo

  5. Si es necesario, no dude en usar los márgenes de diseño asociados con una vista que participa en la definición de restricción let viewMargins = self.webview.layoutMarginsGuide: es un 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 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 algo en el código, la aplicación se bloqueará porque se requiere 1000)
  8. Considere no intentar usar de inmediato activarConstraints y desactivarConstraints (tienen su lugar, pero cuando solo está aprendiendo o si está usando un guión gráfico con estos, probablemente significa que está haciendo demasiado ~ aunque sí 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 diseño 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 tiempo perdido construyendo restricciones con la nueva clase y subclases NSAnchorLayout. 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, entonces seguramente use este método para agregar sus restricciones:

Muestra rápida de soluciones a EVITAR cuando se utilizan 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 suceda nada: en cuyo caso, puede tener una solución medio cocida como esta:

NB: Tómese un momento para leer la sección AutoLayout a continuación y la guía original. Hay una manera de utilizar estas técnicas para complementar sus 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 diseño automático (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 los cambios de animación realizados por Auto Layout, debe hacer que sus restricciones cambien mediante programación. El concepto básico es el mismo tanto para iOS como para OS X, pero hay algunas diferencias menores.

En una aplicación de iOS, su código se vería así:

[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 capas, debe animar la constante utilizando el animador de la restricción:

[[constraint animator] setConstant:42];

Para aquellos que aprenden mejor visualmente, vean 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 dinámicos es una gran idea.

Buena suerte y que la fuerza te acompañe.

Tommie C.
fuente
12

Solución rápida:

yourConstraint.constant = 50
UIView.animate(withDuration: 1.0, animations: {
    yourView.layoutIfNeeded
})
Nazariy Vlizlo
fuente
7

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 líneas que he usado en todas mis aplicaciones para animarlas correctamente. Algunas soluciones aquí no funcionan, debe 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.
}
C0mrade
fuente
4

Estaba tratando de animar restricciones y no fue realmente fácil encontrar una buena explicación.

Lo que dicen otras respuestas es totalmente cierto: debe llamar al [self.view layoutIfNeeded];interior animateWithDuration: animations:. Sin embargo, el otro punto importante es tener punteros para todo lo NSLayoutConstraintque quieras animar.

Creé un ejemplo en GitHub .

Gabriel.Massana
fuente
4

Solución funcional 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 customView (CalendarView). Llamé a .layoutIfNeeded () en self.view y NO en self.calendarView

Espero que esto ayude.

Edoardo Vicoli
fuente
3

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

En el cual, codificó así:

- (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.

DD
fuente
1

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 de teclado abierto.

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

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

Se me ocurrió que podría haber algún diseño adicional como resultado de la apertura del teclado. Agregar un simple bloque dispatch_after con un retraso de 10 ms hizo que la animación se ejecutara siempre, sin saltar.

ivanferdelja
fuente