Comment animer les changements de contraintes?
je mets à jour une vieille application avec un AdBannerView
et quand il n'y a pas de publicité, elle glisse hors de l'écran. Quand il ya une annonce, il glisse sur l'écran. Des trucs de base.
Style Ancien, j'ai placé le cadre dans un bloc d'animation.
Nouveau style, j'ai un IBOutlet
à la contrainte qui détermine la position Y, dans ce cas c'est la distance du bas de la superview, et modifier la constante.
- (void)moveBannerOffScreen {
[UIView animateWithDuration:5
animations:^{
_addBannerDistanceFromBottomConstraint.constant = -32;
}];
bannerIsVisible = FALSE;
}
- (void)moveBannerOnScreen {
[UIView animateWithDuration:5
animations:^{
_addBannerDistanceFromBottomConstraint.constant = 0;
}];
bannerIsVisible = TRUE;
}
Et la bannière se déplace, exactement comme prévu, mais pas d'animation.
mise à JOUR: j'ai re-regardé WWDC12 vidéo " Meilleures Pratiques pour la maîtrise de la Mise en page Automatique " qui couvre l'animation. Il explique comment mettre à jour les contraintes en utilisant CoreAnimation
.
j'ai essayé avec le code suivant, mais obtenir les mêmes résultats.
- (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;
}
sur une note latérale, Je ont vérifié de nombreuses fois et ceci est exécuté sur le thread principal.
13 réponses
deux notes importantes:
-
vous devez appeler
layoutIfNeeded
dans le bloc d'animation. Apple recommande en fait de l'appeler une fois avant le bloc d'animation pour s'assurer que toutes les opérations de mise en page en attente ont été effectuées -
vous devez l'appeler spécifiquement sur le vue de parent (par exemple
self.view
), pas la vue de l'enfant qui a les contraintes attachées à il. Faire ainsi mettra à jour toutes vues limitées, y compris animer d'autres vues qui pourraient être limitées à la vue que vous avez changé la contrainte de (par exemple, la vue B est attachée au bas de la vue A et vous venez de changer l'offset supérieur de la vue A et vous voulez que la vue B animer avec elle)
Essayez ce qui suit:
Objectif-C
- (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()
}
j'apprécie la réponse, mais je pense qu'il serait bien de prendre un peu plus loin.
l'animation de bloc de base de la documentation
[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
}];
, mais vraiment c'est un scénario très simpliste. Que faire si je veux animer les contraintes de subview via la méthode updateConstraints
?
un bloc d'animation qui appelle la méthode updateConstraints
[self.view layoutIfNeeded];
[self.subView setNeedsUpdateConstraints];
[self.subView updateConstraintsIfNeeded];
[UIView animateWithDuration:1.0f delay:0.0f options:UIViewAnimationOptionLayoutSubviews animations:^{
[self.view layoutIfNeeded];
} completion:nil];
le la méthode updateConstraints est dépassée dans la sous-classe UIView et doit appeler super à la fin de la méthode.
- (void)updateConstraints
{
// Update some constraints
[super updateConstraints];
}
La mise en page automatique Guide laisse beaucoup à désirer, mais il vaut la peine de lire. Je l'utilise moi-même dans le cadre d'un UISwitch
qui fait basculer une subview avec une paire de UITextField
s avec une animation d'effondrement simple et subtile (0.2 seconde). Les contraintes de subview sont traitées dans les sous-classes UIView les méthodes updateConstraints décrites ci-dessus.
Généralement, il suffit de mettre à jour les contraintes et d'appeler layoutIfNeeded
dans le bloc d'animation. Il peut s'agir de modifier la propriété .constant
d'un NSLayoutConstraint
, d'Ajouter Supprimer des contraintes (iOS 7), ou de modifier la propriété .active
de contraintes (iOS 8 & 9).
Code Échantillon:
[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];
}];
Exemple De Configuration:
Controverse
on se demande si la contrainte doit être changée avant le bloc d'animation, ou à l'intérieur it (voir les réponses précédentes).
ce qui suit est une conversation sur Twitter entre Martin Pilkington qui enseigne à iOS, et Ken Ferry qui a écrit Auto Layout. Ken explique que, bien que changeant les constantes en dehors de la bloc d'animation peut actuellement travailler, ce n'est pas sûr et ils devraient vraiment être changer à l'intérieur le bloc d'animation. https://twitter.com/kongtomorrow/status/440627401018466305
Animation:
Exemple De Projet
voici un projet simple montrant comment une vue peut être animé. Il utilise L'Objectif C et anime la vue en changeant la propriété .active
de plusieurs contraintes.
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, du Code, des Conseils et quelques Astuces
les autres réponses sont très bien, mais celle-ci met en évidence quelques gotchas assez importantes de l'animation des contraintes à l'aide d'un exemple récent. Je suis passé par beaucoup de variations avant de réaliser ce qui suit:
faire les contraintes que vous voulez cibler en variables de classe pour tenir une référence forte. Dans Swift, j'ai utilisé des variables paresseuses:
lazy var centerYInflection:NSLayoutConstraint = {
let temp = self.view.constraints.filter({ "151900920".firstItem is MNGStarRating }).filter ( { "151900920".secondItem is UIWebView }).filter({ "151900920".firstAttribute == .CenterY }).first
return temp!
}()
après quelques expérimentation j'ai noté que l'on doit obtenir la contrainte de la vue au-dessus de (aka la superview) les deux vues où la contrainte est définie. Dans l'exemple ci-dessous (mngstarrating et UIWebView sont les deux types d'éléments que je crée une contrainte entre, et ils sont des sous-vues en soi.vue.)
Chaîne De Filtres
je profite de la méthode de filtrage de Swift pour séparer les souhaité contrainte qui servira de point d'inflexion. On pourrait aussi devenir beaucoup plus compliqué mais filter fait un bon travail ici.
Animation Des Contraintes De L'Utilisation De Swift
Nota Bene - Cet exemple est la table de montage séquentiel/solution de code et assume on a fait des contraintes par défaut dans le storyboard. On peut alors animer les modifications en utilisant le code.
en supposant que vous créer une propriété pour filtrer avec des critères précis et obtenir un point d'inflexion spécifique pour votre animation (bien sûr, vous pouvez également filtrer pour un tableau et boucle si vous avez besoin de multiples contraintes):
lazy var centerYInflection:NSLayoutConstraint = {
let temp = self.view.constraints.filter({ "151910920".firstItem is MNGStarRating }).filter ( { "151910920".secondItem is UIWebView }).filter({ "151910920".firstAttribute == .CenterY }).first
return temp!
}()
....
plus tard...
@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
}
}
}
Le nombre de mauvais tours
Ces notes sont vraiment un ensemble de conseils que j'ai écrit pour moi-même. J'ai fait tout le don'ts personnellement et douloureusement. Espérons que ce guide pourra en épargner d'autres.
-
attention à la position Z. Parfois, quand rien n'est apparemment happening, vous devriez cacher certaines des autres vues ou utiliser la vue débogueur pour localiser votre vue animée. J'ai même trouvé des cas où un utilisateur a défini Runtime Attribut a été perdu dans le xml d'un storyboard et a conduit à l'animé vue d'être recouverts (tout en travaillant).
-
toujours prendre une minute pour lire la documentation (nouvelle et ancienne), vite De l'aide, et les en-têtes. Apple continue de faire beaucoup de changements pour mieux Gérer les contraintes D'AutoLayout (voir vues stack). Ou au moins le Autolayout Cookbook . Gardez à l'esprit que parfois les meilleures solutions se trouvent dans les anciennes documentations/vidéos.
-
jouer avec les valeurs de l'animation et envisager d'utiliser autres animauxavec des variantes de duration.
-
Ne pas coder en dur la mise en page spécifique de valeurs comme critères pour déterminer changements à d'autres constantes, utilisez plutôt des valeurs qui vous permettent de déterminer l'emplacement de la vue.
CGRectContainsRect
est un exemple - si nécessaire, n'hésitez pas à utiliser les marges de mise en page
une vue de participer à la définition de la contrainte
let viewMargins = self.webview.layoutMarginsGuide
: est à l'exemple - ne pas faire le travail, vous n'avez pas à le faire, tous les vues avec des contraintes sur les storyboard ont des contraintes attachées à la propriété auto.viewName.contraintes
- changez vos priorités pour les contraintes à moins de 1000. J'ai mis mine à 250 (bas) ou 750 (haut) sur le storyboard; (si vous essayez de changer une priorité de 1000 à quoi que ce soit dans le code, puis l'application se crash parce que 1000 est nécessaire)
- envisager de ne pas essayer d'utiliser immédiatement activateConstraints et ils disposent de leurs propres lieu mais quand juste apprendre ou si vous utilisez un storyboard en utilisant ceux-ci signifie probablement que vous faites trop ~ ils ont une place bien que comme vu ci-dessous)
- N'utilisez pas addConstraints / removeConstraints à moins que vous ne soyez vraiment ajouter une nouvelle contrainte dans le code. J'ai constaté que la plupart du temps je la disposition des vues dans le storyboard avec les contraintes souhaitées (placer la vue offscreen), puis en code, j'anime les contraintes précédemment créées dans le storyboard pour déplacer le point de vue autour de.
- j'ai passé beaucoup de temps perdu à construire des contraintes avec le nouveau NSAnchorLayout classe et sous-classes. Celles-ci fonctionnent très bien, mais il ça m'a pris du temps de réaliser que toutes les contraintes dont j'avais besoin il existait déjà dans le storyboard. Si vous construisez des contraintes en code puis très certainement utiliser cette méthode pour agréger vos contraintes:
échantillon rapide de Solutions à éviter lors de L'utilisation de 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({
("151930920".firstItem is UIButton || "151930920".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 vous oubliez un de ces bouts ou les plus simples comme où ajouter le layoutifned, très probablement rien ne se passera: dans ce cas, vous pouvez avoir une solution à moitié cuit comme ceci:
NB - prendre un moment pour lire la Section AutoLayout ci-dessous et le guide original. Il y a une façon d'utiliser ces techniques pour compléter vos animateurs dynamiques.
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
}
}
extrait du Guide AutoLayout (notez que le second extrait est pour utiliser OS X). BTW - ce n'est plus dans le guide actuel aussi loin que je peux voir. les techniques préférées continuent d'évoluer.
Animation des Modifications Apportées par la Mise en page Automatique
si vous avez besoin d'un contrôle total sur l'animation des modifications faites par la mise en page automatique, vous devez faire vos modifications de contraintes programmatiquement. Base le concept est le même pour iOS et OS X, mais il y a quelques différences mineures.
dans une application iOS, votre code ressemblerait à quelque chose comme ceci:
[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
}];
dans OS X, utilisez le code suivant lors de l'utilisation des animations de calques:
[containterView layoutSubtreeIfNeeded];
[NSAnimationContext runAnimationGroup:^(NSAnimationContext *context) {
[context setAllowsImplicitAnimation: YES];
// Make all constraint changes here
[containerView layoutSubtreeIfNeeded];
}];
quand vous n'utilisez pas les animations en calques, vous devez animer la constante en utilisant l'animateur de la contrainte:
[[constraint animator] setConstant:42];
pour ceux qui apprennent mieux visuellement vérifier ce début vidéo D'Apple .
Faites Très Attention
souvent dans la documentation il y a des petites notes ou des morceaux de code qui mènent à des idées plus grandes. Par exemple, attacher des contraintes de mise en page automatique aux animateurs dynamiques est une grande idée.
bonne chance et que la Force soit avec vous.
Swift 4 solution
trois étapes simples:
-
Modifier les contraintes, p.ex.:
heightAnchor.constant = 50
-
dire au contenant
view
que sa disposition est sale et que l'autolayout devrait recalculer la disposition:self.view.setNeedsLayout()
-
dans le bloc d'animation, dites à la mise en page de recalculer la mise en page, ce qui équivaut à définir les cadres directement (dans ce cas, l'autolayout définira les cadres):
UIView.animate(withDuration: 0.5) { self.view.layoutIfNeeded() }
Complete simple example:
heightAnchor.constant = 50
self.view.setNeedsLayout()
UIView.animate(withDuration: 0.5) {
self.view.layoutIfNeeded()
}
Note
il y a un 0ème pas optionnel-avant de changer les contraintes vous vous pouvez appeler self.view.layoutIfNeeded()
pour vous assurer que le point de départ de l'animation est l'état avec les anciennes contraintes appliquées (dans le cas où il y avait d'autres changements de contraintes qui ne devraient pas être inclus dans l'animation):
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()
}
Puisqu'avec iOS 10 nous avons un nouveau mécanisme d'animation - UIViewPropertyAnimator
, nous devrions savoir que fondamentalement le même mécanisme s'applique à elle. Les étapes sont essentiellement les mêmes:
heightAnchor.constant = 50
self.view.setNeedsLayout()
let animator = UIViewPropertyAnimator(duration: 0.5, timingParameters: UICubicTimingParameters(animationCurve: .linear))
animator.addAnimations {
self.view.layoutIfNeeded()
}
animator.startAnimation()
Depuis animator
est une encapsulation de l'animation, nous pouvons faire référence à elle et l'appeler plus tard. Cependant, puisque dans le bloc d'animation nous disons simplement à l'autolayout de recalculer les frames, nous devons changer les contraintes avant d'appeler startAnimation
. Par conséquent quelque chose comme ceci est possible:
// 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()
L'ordre de l'évolution des contraintes et le démarrage d'une l'animateur est important - si nous changeons simplement les contraintes et laissons notre animateur pour un point ultérieur, le prochain cycle de redessination peut invoquer le recalcul autolayout et le changement ne sera pas animé.
Aussi, n'oubliez pas qu'un simple animateur est non-réutilisable - une fois que vous l'exécutez, vous ne pouvez pas "réexécuter". Donc je suppose qu'il n'y a pas vraiment de bonne raison de garder l'animateur autour, à moins que nous l'utilisions pour contrôler une animation interactive.
solution Swift:
yourConstraint.constant = 50
UIView.animateWithDuration(1, animations: yourView.layoutIfNeeded)
Working Solution 100% Swift 3.1
j'ai lu toutes les réponses et je veux partager le code et la hiérarchie des lignes que j'ai utilisées dans toutes mes applications pour les animer correctement, certaines solutions ici ne fonctionnent pas, vous devriez les vérifier sur les appareils plus lents E. g iPhone 5 en ce moment.
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.
}
j'essayais d'animer les contraintes et n'étais pas vraiment facile à trouver une bonne explication.
ce que d'autres réponses disent est totalement vrai: vous devez appeler [self.view layoutIfNeeded];
à l'intérieur de animateWithDuration: animations:
. Cependant, l'autre point important est d'avoir des pointeurs pour tous NSLayoutConstraint
que vous souhaitez animer.
solution de travail et vient d'être testée pour Swift 3 avec 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)
il suffit de garder à l'esprit ce soi.calendarViewHeight est une contrainte référencée à un customView (CalendarView). J'ai appelé le .layoutIfNeeded() sur l'auto.vue et PAS sur de soi.calendarView
Espérons que cette aide.
il y a un article à ce sujet: http://weblog.invasivecode.com/post/42362079291/auto-layout-and-core-animation-auto-layout-was
dans lequel, il a codé comme ceci:
- (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];
}];
}
}
J'espère que ça aidera.
dans le contexte de l'animation contrainte, je voudrais mentionner une situation spécifique où j'ai animé une contrainte immédiatement dans une notification keyboard_opened.
La contraintedéfinit un espace supérieur allant d'un champ de texte au dessus du conteneur. En ouvrant le clavier, je divise la constante par 2.
Je n'ai pas été en mesure de réaliser une animation douce et cohérente des contraintes directement dans la notification clavier. Environ la moitié du temps d'affichage serait simplement sauter à sa nouvelle position-sans animer.
cela m'est venu à l'esprit qu'il pourrait y avoir des mises en scène supplémentaires à la suite de l'ouverture du clavier. L'ajout d'un bloc dispatch_after simple avec un délai de 10ms a fait courir l'animation à chaque fois - pas de saut.
pour les autres Xamariens, voici le Xamarin.iOS / C # version:
UIView.Animate(5, () =>
{
_addBannerDistanceFromBottomConstraint.Constant = 0;
View.LayoutIfNeeded();
});