ios - bottom - status bar iphone
¿Cómo animo cambios de restricción? (13)
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.
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).
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.
Juega con los valores en la animación y considera usar otras variantes animateWithDuration.
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- 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 - 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
- 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)
- 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)
- 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.
- 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.
Dos notas importantes:
layoutIfNeeded
llamar alayoutIfNeeded
dentro del bloque de animación. Apple 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 pendientesself.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 por 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 ello)
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()
}
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
Estaba tratando de animar las restricciones y no fue realmente fácil encontrar una buena explicación.
Lo que dicen otras respuestas es totalmente cierto: debe llamar [self.view layoutIfNeeded];
dentro de animateWithDuration: animations:
Sin embargo, el otro punto importante es tener punteros para cada NSLayoutConstraint
que desee animar.
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 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 que se refiere a un customView (CalendarView). Llamé a .layoutIfNeeded () en self.view y NOT en self.calendarView
Espero que esto ayude.
Solución rápida:
yourConstraint.constant = 50
UIView.animateWithDuration(1, animations: yourView.layoutIfNeeded)
Solución Swift 4
Tres simples pasos:
Cambiar las restricciones, por ejemplo:
heightAnchor.constant = 50
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()
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()
}
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.
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, 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.
}
// 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];
}];