197 votes

Les NSLayoutConstraints sont-ils animables ?

J'essaie d'animer certaines vues pour qu'elles soient bloquées par le clavier géant en paysage. Cela fonctionne bien si je me contente d'animer les images, mais d'autres personnes ont suggéré que cela était contre-productif et que je devrais plutôt mettre à jour les NSLayoutConstraints. Cependant, elles ne semblent pas pouvoir être animées. Quelqu'un a-t-il réussi à les faire fonctionner ?

//heightFromTop is an NSLayoutConstraint referenced from IB
[UIView animateWithDuration:0.25 animations:^{
    self.heightFromTop.constant= 550.f;
}];

Le résultat est un saut instantané à la hauteur en question.

473voto

John Estropia Points 6718

Il suffit de suivre ce modèle exact :

self.heightFromTop.constant = 550.0f;
[myView setNeedsUpdateConstraints];

[UIView animateWithDuration:0.25f animations:^{
   [myView layoutIfNeeded];
}];

myView est la vue où self.heightFromTop a été ajouté. Votre vue "saute" parce que la seule chose que vous avez faite dans le bloc d'animation a été de définir la contrainte, ce qui ne provoque pas de mise en page immédiate. Dans votre code, la mise en page se produit lors de la boucle d'exécution suivante, après que vous ayez défini la contrainte heightFromTop.constant et à ce moment-là, vous êtes déjà en dehors du champ d'application du bloc d'animation.

81voto

Centurion Points 3183

La méthode suggérée par Apple est un peu différente ( Voir l'exemple dans la section "Animer les modifications apportées par la mise en page automatique". ). Vous devez d'abord appeler layoutIfNeeded avant l'animation. Ensuite, ajoutez vos éléments d'animation à l'intérieur du bloc d'animation, puis appelez à nouveau layoutIfNeeded à la fin. Pour ceux qui, comme moi, sont en train de passer à l'autolayout, c'est une méthode plus similaire aux animations précédentes que nous faisions avec les cadres à l'intérieur des blocs d'animation. Nous devons simplement appeler layoutIfNeeded deux fois - avant les animations et après les animations :

[self.view layoutIfNeeded]; // Ensures that all pending layout operations have been completed

[UIView animateWithDuration:1.0f animations:^{

  // Make all constraint changes here
  self.heightFromTop.constant= 550.f;

  [self.view layoutIfNeeded]; // Forces the layout of the subtree animation block and then captures all of the frame changes

}];

10voto

Joseph Lin Points 1587

J'ai essayé l'approche de @Centurion, mais d'une manière ou d'une autre, ma vue s'anime sur une mauvaise image si elle est chargée à partir du storyboard. Le problème disparaît si je remplace le premier layoutIfNeeded avec updateConstraintsIfNeeded mais je ne sais pas pourquoi. Si quelqu'un peut donner une explication, ce serait très apprécié.

[self.view updateConstraintsIfNeeded];
[UIView animateWithDuration:1.0 animations:^{
    self.myConstraint.constant= 100;
    [self.view layoutIfNeeded];
}];

4voto

Scott Carter Points 456

J'avais un problème similaire et ce fil de discussion m'a beaucoup aidé à le résoudre.

La réponse d'erurainon m'a mis sur la bonne voie, mais j'aimerais proposer une réponse légèrement différente. Le code suggéré par erurainon n'a pas fonctionné pour moi, car j'avais toujours un saut au lieu d'une transition animée. Le lien fourni par cnotethegr8 m'a donné la réponse qui fonctionne :

Guide de mise en page automatique https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/AutoLayoutbyExample/AutoLayoutbyExample.html (jusqu'au bas de la page).

Quelques différences par rapport à la réponse d'erurainon :

  1. Appelez layoutIfNeeded sur la vue du conteneur avant l'appel à une méthode d'animation (et au lieu de setNeedsUpdateConstraints sur myView).
  2. Définissez la nouvelle contrainte dans le bloc d'animations.
  3. Appelez layoutIfNeeded sur la vue du conteneur dans la méthode animations (après avoir défini la contrainte), au lieu de myView.

Cela correspondra au modèle suggéré par Apple dans le lien ci-dessus.

Un exemple

Je voulais animer une vue particulière, en la fermant ou en l'agrandissant par un simple clic sur un bouton. Comme j'utilise autolayout et que je ne voulais pas coder en dur des dimensions (dans mon cas la hauteur) dans le code, j'ai décidé de capturer la hauteur dans viewDidLayoutSubviews. Vous devez utiliser cette méthode et non viewWillAppear lorsque vous utilisez l'autolayout. Comme viewDidLayoutSubviews peut être appelé plusieurs fois, j'ai utilisé un BOOL pour me faire connaître la première exécution de mon initialisation.

// Code snippets

@property (weak, nonatomic) IBOutlet UIView *topView; // Container for minimalView
@property (weak, nonatomic) IBOutlet UIView *minimalView; // View to animate

@property (nonatomic) CGFloat minimalViewFullHeight; // Original height of minimalView

@property (weak, nonatomic) IBOutlet NSLayoutConstraint *minimalViewHeightConstraint;

@property (nonatomic) BOOL executedViewDidLayoutSubviews;

- (void)viewDidLayoutSubviews
{
    [super viewDidLayoutSubviews];

    // First execution of viewDidLayoutSubviews?
    if(!self.executedViewDidLayoutSubviews){
        self.executedViewDidLayoutSubviews = YES;

        // Record some original dimensions
        self.minimalViewFullHeight = self.minimalView.bounds.size.height;

        // Setup our initial view configuration & let system know that 
        // constraints need to be updated.
        self.minimalViewHeightConstraint.constant = 0.0;
        [self.minimalView setNeedsUpdateConstraints];

        [self.topView layoutIfNeeded];
    }
}

Redimensionner l'extrait d'action complet

// An action to close our minimal view and show our normal (full) view
- (IBAction)resizeFullAction:(UIButton *)sender {

    [self.topView layoutIfNeeded];

    [UIView transitionWithView:self.minimalView
                  duration:1.0
                   options:UIViewAnimationOptionTransitionCrossDissolve
                animations:^{
                    self.minimalViewHeightConstraint.constant = 0.0;

                    // Following call to setNeedsUpdateConstraints may not be necessary
                    [self.minimalView setNeedsUpdateConstraints];

                    [self.topView layoutIfNeeded];

                } completion:^(BOOL finished) {
                    ;
                }];

    // Other code to show full view
    // ...
}

Redimensionner un petit extrait d'action

// An action to open our minimal view and hide our normal (full) view
- (IBAction)resizeSmallAction:(UIButton *)sender {

    [self.topView layoutIfNeeded];

    [UIView transitionWithView:self.minimalView
                  duration:1.0
                   options:UIViewAnimationOptionTransitionCrossDissolve
                animations:^{
                    self.minimalViewHeightConstraint.constant = self.minimalViewFullHeight;
                    [self.minimalView setNeedsUpdateConstraints];

                    [self.topView layoutIfNeeded];

                } completion:^(BOOL finished) {
                    ;
                }];

        // Other code to hide full view
        // ...
    }

Vous pouvez utiliser animateWithDuration au lieu de transitionWithView si vous le souhaitez.

J'espère que cela vous aidera.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X