Comment animer les changements de contrainte?

959

Je mets à jour une ancienne application avec un AdBannerViewet quand il n'y a pas d'annonce, elle glisse hors écran. Lorsqu'une annonce est diffusée, elle glisse sur l'écran. Trucs de base.

À l'ancienne, j'ai mis le cadre dans un bloc d'animation. Nouveau style, j'ai une IBOutletcontrainte de mise en page automatique qui détermine la Yposition, dans ce cas, c'est la distance par rapport au bas de la vue d'ensemble et modifie 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 bouge, exactement comme prévu, mais pas d' animation.


MISE À JOUR: J'ai revu la WWDC 12 parler des meilleures pratiques pour maîtriser la mise en page automatique qui couvre l'animation. Il explique comment mettre à jour les contraintes à l'aide de CoreAnimation :

J'ai essayé avec le code suivant, mais obtenez exactement 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, j'ai vérifié plusieurs fois et cela est en cours d'exécution sur le thread principal .

DBD
la source
11
Je n'ai jamais vu autant de votes proposés pour une question et une réponse sur une faute de frappe sur SO avant
abbood
3
S'il y a une faute de frappe dans la réponse, vous devez modifier la réponse. C'est pourquoi ils sont modifiables.
i_am_jorf
@jeffamaphone - Ce serait plus utile si vous montriez la faute de frappe donc je savais où était l'erreur. Vous pouvez modifier la réponse vous-même et corriger la faute de frappe en sauvant tout le monde notre diatribe. Je viens de le modifier pour supprimer la constante du bloc d'animation, si c'est à cela que vous faisiez référence.
DBD
1
Je ne sais pas quelle est la faute de frappe. Je répondais aux commentaires ci-dessus.
i_am_jorf
9
Ensuite, la faute de frappe est la question. Bêtement, je tapais "setNeedsLayout" au lieu de "layoutIfNeeded". Cela apparaît clairement dans ma question lorsque je coupe et colle mon code avec l'erreur et les captures d'écran avec la commande correcte. Pourtant, je ne pouvais pas le remarquer jusqu'à ce que quelqu'un le fasse remarquer.
DBD

Réponses:

1697

Deux notes importantes:

  1. Vous devez appeler layoutIfNeededdans le bloc d'animation. Apple vous recommande en fait de l'appeler une fois avant le bloc d'animation pour vous assurer que toutes les opérations de mise en page en attente ont été terminées

  2. Vous devez l'appeler spécifiquement sur la vue parent (par exemple self.view), pas sur la vue enfant qui a les contraintes qui lui sont attachées. Cela mettra à jour toutes les vues contraintes, y compris l'animation d'autres vues qui pourraient être limitées à la vue dont vous avez modifié la contrainte (par exemple, la vue B est attachée au bas de la vue A et vous venez de changer le décalage supérieur de la vue A et vous voulez la vue B animer avec)

Essaye ça:

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()
}
g3rv4
la source
200
Vous savez quoi ... votre réponse fonctionne. La WWDC fonctionne ... ma vision échoue. Pour une raison quelconque, il m'a fallu une semaine pour réaliser que j'appelais à la setNeedsLayoutplace de layoutIfNeeded. Je suis légèrement horrifié par le nombre d'heures que j'ai passées sans remarquer que je viens de taper le mauvais nom de méthode.
DBD
23
La solution fonctionne mais vous n'avez pas besoin de modifier la constante de contrainte dans le bloc d'animation. Il est tout à fait correct de définir la contrainte une fois avant de lancer l'animation. Vous devez modifier votre réponse.
Ortwin Gentz
74
Cela n'a pas fonctionné pour moi au départ, puis j'ai réalisé que vous devez appeler layoutIfNeeded sur la vue PARENT, pas sur la vue à laquelle les contraintes s'appliquent.
Oliver Pearmain
20
l'utilisation de layoutIfNeeded animera toutes les actualisations de sous-vue et pas seulement le changement de contrainte. comment n'animez-vous que le changement de contrainte?
ngb
11
«Apple vous recommande en fait de l'appeler une fois avant le bloc d'animation pour vous assurer que toutes les opérations de mise en page en attente sont terminées», merci, je n'y ai jamais pensé, mais cela a du sens.
Rick van der Linde
109

J'apprécie la réponse fournie, mais je pense que ce serait bien d'aller 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 c'est vraiment un scénario très simpliste. Et si je veux animer des contraintes de sous-vue via le updateConstraints méthode?

Un bloc d'animation qui appelle la méthode subviews 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];

La méthode updateConstraints est substituée dans la sous-classe UIView et doit appeler super à la fin de la méthode.

- (void)updateConstraints
{
    // Update some constraints

    [super updateConstraints];
}

Le guide AutoLayout laisse beaucoup à désirer, mais il vaut la peine d'être lu. Je l'utilise moi-même dans le cadre d'un UISwitchqui bascule une sous-vue avec une paire de UITextFields avec une animation d'effondrement simple et subtile (0,2 seconde). Les contraintes de la sous-vue sont gérées dans les méthodes updateConstraints des sous-classes UIView comme décrit ci-dessus.

Cameron Lowell Palmer
la source
Lors de l'appel de toutes les méthodes ci-dessus self.view(et non sur une sous-vue de cette vue), l'appel updateConstraintsIfNeededn'est pas nécessaire (car setNeedsLayoutil déclenche également updateConstraintscette vue). Cela pourrait être trivial pour la plupart, mais ce n'était pas pour moi jusqu'à présent;)
anneblue
Il est difficile de commenter sans connaître l'interaction complète de la mise en page automatique et de la disposition des ressorts et des entretoises dans votre cas particulier. Je parle entièrement d'un pur scénario de mise en page automatique. Je serais curieux de savoir ce qui se passe exactement dans votre méthode layoutSubviews.
Cameron Lowell Palmer
translatesAutoresizingMaskIntoConstraints = NO; Si vous souhaitez une mise en page automatique pure, vous devez absolument la désactiver.
Cameron Lowell Palmer
Je ne l'ai pas vu, mais je ne peux pas vraiment parler du problème sans un peu de code. Peut-être pourriez-vous simuler une TableView et la coller sur GitHub?
Cameron Lowell Palmer
Désolé, je n'utilise pas gitHub :(
anneblue
74

Généralement, il vous suffit de mettre à jour les contraintes et d'appeler layoutIfNeededà l'intérieur du bloc d'animation. Cela peut être soit en changeant la .constantpropriété d'un NSLayoutConstraint, en ajoutant des contraintes de suppression (iOS 7), soit en changeant la .activepropriété des contraintes (iOS 8 et 9).

Exemple de code:

[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:

Projet Xcode donc exemple de projet d'animation.

Controverse

Il y a des questions à savoir si la contrainte doit être modifiée avant le bloc d'animation, ou à l' intérieur (voir les réponses précédentes).

Ce qui suit est une conversation Twitter entre Martin Pilkington qui enseigne iOS et Ken Ferry qui a écrit Auto Layout. Ken explique que même si la modification des constantes en dehors du bloc d'animation peut actuellement fonctionner, ce n'est pas sûr et elles devraient vraiment être modifiées à l' intérieur du bloc d'animation. https://twitter.com/kongtomorrow/status/440627401018466305

Animation:

Exemple de projet

Voici un projet simple montrant comment une vue peut être animée. Il utilise l'objectif C et anime la vue en modifiant la .activepropriété de plusieurs contraintes. https://github.com/shepting/SampleAutoLayoutAnimation

Steven Hepting
la source
+1 pour afficher un exemple utilisant le nouveau drapeau actif. De plus, changer la contrainte en dehors du bloc d'animation m'a toujours semblé un hack
Korey Hinton
J'ai soulevé un problème dans github, car cette solution ne fonctionne pas pour ramener la vue à son emplacement actuel. Je pense que le changement d'actif n'est pas la bonne solution et vous devriez plutôt changer la priorité. Définissez le haut à 750 et le bas à 250, puis dans le code, alternez entre UILayoutPriorityDefaultHigh et UILayoutPriorityDefaultLow.
malhal
Une autre raison de la mise à jour à l'intérieur du bloc est qu'elle isole les modifications du code appelant qui peuvent également appeler layoutIfNeeded. (et merci pour le lien Twitter)
Chris Conover
1
Très bonne réponse. Surtout parce que vous mentionnez la conversation de Martin et Ken à ce sujet.
Jona
Je suis confus quant à la manière de mettre à jour les contraintes et j'ai écrit ceci . Pouvez-vous s'il vous plaît jeter un oeil?
Honey
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
la source
29

Solution Swift 4

UIView.animate

Trois étapes simples:

  1. Modifiez les contraintes, par exemple:

    heightAnchor.constant = 50
  2. Dites au viewconteneur que sa disposition est sale et que la mise en page automatique doit recalculer la disposition:

    self.view.setNeedsLayout()
  3. Dans le bloc d'animation, dites à la disposition de recalculer la disposition, ce qui équivaut à définir directement les images (dans ce cas, la mise en page automatique définira les images):

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

Exemple le plus simple et complet:

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

Sidenote

Il y a une 0ème étape facultative - avant de modifier les contraintes que vous voudrez peut-être appeler self.view.layoutIfNeeded()pour vous assurer que le point de départ de l'animation provient de l'état avec les anciennes contraintes appliquées (au cas où il y aurait 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()
}

UIViewPropertyAnimator

Comme avec iOS 10, nous avons un nouveau mécanisme d'animation - UIViewPropertyAnimator, nous devons savoir que le même mécanisme s'applique essentiellement à lui. Les étapes sont fondamentalement 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()

Puisqu'il animators'agit d'une encapsulation de l'animation, nous pouvons garder une référence à elle et l'appeler plus tard. Cependant, comme dans le bloc d'animation, nous disons simplement à la mise en page automatique de recalculer les images, nous devons changer les contraintes avant d'appeler startAnimation. Par conséquent, quelque chose comme ça 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 modification des contraintes et de démarrage d'un animateur est important - si nous modifions simplement les contraintes et laissons notre animateur pour un point ultérieur, le prochain cycle de rafraîchissement peut invoquer le recalcul de la mise en page automatique et le changement ne sera pas animé.

Rappelez-vous également qu'un seul animateur n'est pas réutilisable - une fois que vous l'exécutez, vous ne pouvez pas le "réexécuter". Donc je suppose qu'il n'y a pas vraiment de bonne raison de garder l'animateur, sauf si nous l'utilisons pour contrôler une animation interactive.

Milan Nosáľ
la source
Great fonctionne très bien dans swift 5 aussi
spnkr
layoutIfNeeded () est la clé
Medhi
15

Storyboard, Code, Astuces et quelques Gotchas

Les autres réponses sont très bien mais celle-ci met en évidence quelques accrochages assez importants de contraintes d'animation en utilisant un exemple récent. J'ai subi beaucoup de variations avant de réaliser ce qui suit:

Créez les contraintes que vous souhaitez cibler dans les variables de classe pour conserver une référence forte. Dans Swift, j'ai utilisé des variables paresseuses:

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!
}()

Après une certaine expérimentation, j'ai noté que l'on DOIT obtenir la contrainte de la vue AU-DESSUS (alias 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 entre lesquels je crée une contrainte, et ce sont des sous-vues dans self.view).

Chaînage de filtres

Je profite de la méthode de filtrage de Swift pour séparer la contrainte souhaitée qui servira de point d'inflexion. On pourrait aussi devenir beaucoup plus compliqué, mais le filtre fait du bon travail ici.

Animation des contraintes à l'aide de Swift

Nota Bene - Cet exemple est la solution de storyboard / code et suppose que l'on a fait des contraintes par défaut dans le storyboard. On peut alors animer les changements à l'aide de code.

En supposant que vous créez une propriété pour filtrer avec des critères précis et atteindre un point d'inflexion spécifique pour votre animation (bien sûr, vous pouvez également filtrer pour un tableau et boucler si vous avez besoin de plusieurs contraintes):

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!
}()

....

Un peu 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

        }
    }
}

Les nombreux mauvais virages

Ces notes sont vraiment un ensemble de conseils que j'ai écrits pour moi. J'ai fait tous les trucs personnellement et douloureusement. J'espère que ce guide pourra en épargner d'autres.

  1. Attention à zPositioning. Parfois, lorsque rien ne se passe, vous devez masquer certaines des autres vues ou utiliser le débogueur de vues pour localiser votre vue animée. J'ai même trouvé des cas où un attribut d'exécution défini par l'utilisateur a été perdu dans le xml d'un storyboard et a conduit à la vue animée couverte (pendant le travail).

  2. Prenez toujours une minute pour lire la documentation (nouvelle et ancienne), l'aide rapide et les en-têtes. Apple continue d'apporter de nombreuses modifications pour mieux gérer les contraintes de mise en page automatique (voir les vues de pile). Ou au moins le livre de recettes AutoLayout . Gardez à l'esprit que les meilleures solutions se trouvent parfois dans les anciennes documentations / vidéos.

  3. Jouez avec les valeurs de l'animation et envisagez d'utiliser d'autres variantes animateWithDuration.

  4. Ne codez pas en dur des valeurs de mise en page spécifiques comme critères pour déterminer les modifications apportées à d'autres constantes, utilisez plutôt des valeurs qui vous permettent de déterminer l'emplacement de la vue. CGRectContainsRectest un exemple

  5. Si besoin, n'hésitez pas à utiliser les marges de mise en page associées à une vue participant à la définition de la contrainte let viewMargins = self.webview.layoutMarginsGuide: c'est l'exemple
  6. Ne faites pas de travail que vous n'avez pas à faire, toutes les vues avec des contraintes sur le storyboard ont des contraintes attachées à la propriété self.viewName.constraints
  7. Modifiez vos priorités pour toutes les contraintes à moins de 1000. J'ai défini le mien sur 250 (faible) ou 750 (élevé) sur le storyboard; (si vous essayez de changer une priorité de 1000 en quelque chose dans le code, l'application se bloquera car 1000 est requis)
  8. Envisagez de ne pas essayer immédiatement d'utiliser activateConstraints et deactivateConstraints (ils ont leur place, mais lorsque vous apprenez simplement ou si vous utilisez un storyboard, les utiliser signifie probablement que vous en faites trop ~ ils ont une place, comme indiqué ci-dessous)
  9. Envisagez de ne pas utiliser addConstraints / removeConstraints sauf si vous ajoutez réellement une nouvelle contrainte dans le code. J'ai constaté que la plupart du temps, je mets en page les vues dans le storyboard avec les contraintes souhaitées (en plaçant la vue hors écran), puis en code, j'anime les contraintes précédemment créées dans le storyboard pour déplacer la vue.
  10. J'ai passé beaucoup de temps à créer des contraintes avec la nouvelle classe et les sous-classes NSAnchorLayout. Ces travaux fonctionnent très bien mais il m'a fallu un certain temps pour réaliser que toutes les contraintes dont j'avais besoin existaient déjà dans le storyboard. Si vous créez des contraintes dans le code, utilisez certainement cette méthode pour agréger vos contraintes:

Exemple rapide de solutions à éviter lors de l'utilisation de story-boards

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 vous oubliez l'un de ces conseils ou les plus simples tels que l'endroit où ajouter le layoutIfNeeded, rien ne se produira probablement: dans ce cas, vous pouvez avoir une solution à moitié cuite comme celle-ci:

NB - Prenez un moment pour lire la section AutoLayout ci-dessous et le guide original. Il existe un moyen 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 deuxième extrait est pour utiliser OS X). BTW - Ce n'est plus dans le guide actuel pour autant que je puisse voir. Les techniques privilégié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 apportées par la mise en page automatique, vous devez effectuer vos modifications de contrainte par programme. Le concept de base est le même pour iOS et OS X, mais il existe quelques différences mineures.

Dans une application iOS, votre code ressemblerait à 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
}];

Sous OS X, utilisez le code suivant lorsque vous utilisez des animations basées sur des couches:

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

Lorsque vous n'utilisez pas d'animations basées sur des calques, vous devez animer la constante à l'aide de l'animateur de la contrainte:

[[constraint animator] setConstant:42];

Pour ceux qui apprennent mieux, regardez visuellement cette première vidéo d'Apple .

Porter une attention particulière

Souvent, dans la documentation, il y a de petites notes ou des morceaux de code qui mènent à de plus grandes idées. Par exemple, attacher des contraintes de disposition automatique à des animateurs dynamiques est une grande idée.

Bonne chance et que la force soit avec toi.

Tommie C.
la source
12

Solution rapide:

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

Solution de travail 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 des appareils plus lents, par exemple l'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.
}
C0mrade
la source
4

J'essayais d'animer des Contraintes et ce n'était pas vraiment facile de trouver une bonne explication.

Ce que disent les autres réponses est totalement vrai: vous devez appeler à l' [self.view layoutIfNeeded];intérieur animateWithDuration: animations:. Cependant, l'autre point important est d'avoir des pointeurs pour tout NSLayoutConstraintce que vous souhaitez animer.

J'ai créé un exemple dans GitHub .

Gabriel.Massana
la source
4

Solution fonctionnelle et juste 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)

Gardez juste à l'esprit que self.calendarViewHeight est une contrainte référencée à un customView (CalendarView). J'ai appelé le .layoutIfNeeded () sur self.view et NON sur self.calendarView

J'espère que cette aide.

Edoardo Vicoli
la source
3

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 cela aide.

DD
la source
1

Dans le contexte de l'animation de contraintes, je voudrais mentionner une situation spécifique où j'ai animé une contrainte immédiatement dans une notification keyboard_opened.

La contrainte a défini un espace supérieur depuis un champ de texte jusqu'au sommet du conteneur. À l'ouverture du clavier, je divise simplement la constante par 2.

Je n'ai pas pu réaliser une animation de contrainte lisse cohérente directement dans la notification du clavier. Environ la moitié du temps d'affichage passerait simplement à sa nouvelle position - sans animation.

Il m'est apparu qu'il pourrait y avoir une mise en page supplémentaire suite à l'ouverture du clavier. L'ajout d'un simple bloc dispatch_after avec un délai de 10 ms a rendu l'animation exécutée à chaque fois - sans saut.

ivanferdelja
la source