ios - bottom - toolbar xcode




¿Cómo animo cambios de restricción? (9)

Guión gráfico, código, consejos y algunos gotchas.

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

Realice las restricciones que desea orientar en variables de clase para mantener una referencia sólida. 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 algunos experimentos, noté que uno DEBE obtener la restricción de la vista ARRIBA (también conocida como la vista de supervisión) las dos vistas donde se define la restricción. En el siguiente ejemplo (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 del filtro

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

Animando 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. Uno puede entonces animar los cambios usando código.

Suponiendo que creas una propiedad para filtrar con criterios precisos y llegar a un punto de inflexión específico para tu animación (por supuesto, también puedes filtrar una matriz y realizar un bucle si necesitas 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 se hizo personalmente y con dolor. Esperemos que esta guía pueda salvar a otros.

  1. Cuidado con zPositioning. A veces, cuando aparentemente no ocurre nada, debe ocultar algunas de las otras vistas o usar el depurador de la vista 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 llevó a que se cubriera la vista animada (mientras trabajaba).

  2. Tómese un minuto para leer la documentación (nueva y antigua), la Ayuda rápida y los encabezados. Apple sigue realizando muchos cambios para administrar 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 más antiguos.

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

  4. No codifique valores de diseño específicos como criterios para determinar cambios en otras constantes, en lugar de eso, utilice valores que le permitan determinar la ubicación de la vista. CGRectContainsRect es 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 : esté en el ejemplo
  6. No haga el trabajo que no tiene que hacer, todas las vistas con restricciones en el guión gráfico tienen restricciones adjuntas a la propiedad self.viewName.constraints
  7. Cambie sus prioridades para cualquier restricción a menos de 1000. Puse la mía en 250 (baja) o 750 (alta) en el guión gráfico; (Si intenta cambiar una prioridad 1000 a cualquier cosa en el código, la aplicación se bloqueará porque se requiere 1000)
  8. Considere no intentar utilizar de forma inmediata las restricciones de activación y las restricciones de desactivación (tienen su lugar, pero cuando solo están aprendiendo o si está utilizando un guión gráfico, probablemente significa que está haciendo demasiado ~ 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 organizo 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. Esto funciona bien, pero me tomó un tiempo darme cuenta de que todas las restricciones 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 a 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 diseñoSi es Necesario, lo más probable es que no suceda nada: en cuyo caso, puede tener una solución a medias como esta:

NB: tómese un momento para leer la sección de autoenvío 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 siguen evolucionando.

Animando cambios realizados por Auto Layout

Si necesita un control total sobre los cambios de animación realizados por Auto Layout, debe realizar los cambios de restricción mediante programación. El concepto básico es el mismo para iOS y OS X, pero existen algunas diferencias menores.

En una aplicación de iOS, su código se vería como 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 use animaciones respaldadas por capas:

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

Cuando no estés usando animaciones respaldadas por capas, debes 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 piezas de código que llevan 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.

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

Estilo antiguo, coloco el cuadro 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 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 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. CoreAnimation cómo actualizar las restricciones utilizando CoreAnimation .

He intentado con el siguiente código, pero obtén 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, he comprobado varias veces y esto se está ejecutando en el hilo principal.


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

El bloque básico de animación 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 en realidad este es un escenario muy simplista. ¿Qué updateConstraints si deseo animar las restricciones de subvista mediante el método updateConstraints ?

Un bloque de animación que llama al método updateConstraints 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 invalida 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 Autodiseño 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.


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

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

No he podido lograr una animación de restricción suave conistente directamente dentro de la notificación del teclado. Aproximadamente la mitad de las veces la vista solo saltaría a su nueva posición, sin animar.

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 cada vez, sin saltar.


En general, solo necesita actualizar las restricciones y llamar a layoutIfNeeded dentro del bloque de animación. Esto puede ser cambiando la propiedad .constant de una NSLayoutConstraint , agregando restricciones de eliminación (iOS 7) o cambiando la 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 debe cambiarse antes del bloque de animación, o dentro de él (ver las respuestas anteriores).

La siguiente es una conversación de Twitter entre Martin Pilkington que enseña sobre iOS y Ken Ferry, que escribió Auto Layout. Ken explica que aunque las constantes cambiantes fuera del bloque de animación actualmente pueden 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 una vista puede ser animada. Utiliza Objective C y anima la vista cambiando la propiedad .active de varias restricciones. https://github.com/shepting/SampleAutoLayoutAnimation


Hay un artículo que habla 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.


Para los compañeros de Xamarians, aquí está la versión de Xamarin.iOS / C #:

UIView.Animate(5, () =>
{
    _addBannerDistanceFromBottomConstraint.Constant = 0;
    View.LayoutIfNeeded();
});

Solución rápida:

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

Solución Swift 4

UIView.animate

Tres simples pasos:

  1. Cambiar las restricciones, por ejemplo:

    heightAnchor.constant = 50
    
  2. Indique a la view que contiene que 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, indique al diseño que vuelva a calcular el diseño, lo que equivale a configurar los cuadros directamente (en este caso, la distribución automática establecerá los cuadros):

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

Completa el 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 a self.view.layoutIfNeeded() para asegurarse de que el punto de inicio de la animación sea el estado con restricciones antiguas aplicadas (en caso de que existieran otros cambios de restricciones) que no debe 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

Ya 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 animator es una encapsulación de la animación, podemos seguirla y llamarla más tarde. Sin embargo, ya que en el bloque de animación solo le decimos a la reproducción automática que vuelva a calcular los marcos, 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 cambio de restricciones y el inicio de un animador es importante: si solo cambiamos las restricciones y dejamos nuestro animador para algún punto posterior, el siguiente ciclo de redibujado puede invocar el recálculo de autolayout y el cambio no será animado.

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 al animador cerca, a menos que lo usemos para controlar una animación interactiva.


// 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];
}];




autolayout