ios - telecharger - meilleur application iphone gratuite




Comment animer les changements de contraintes? (8)

Storyboard, code, conseils et quelques Gotchas

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

Faites en sorte que les contraintes que vous voulez cibler dans les variables de classe contiennent 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 quelques expérimentations j'ai noté que l'on DOIT obtenir la contrainte à partir de la vue ci- dessus (alias la superview) les deux vues où la contrainte est définie. Dans l'exemple ci-dessous (à la fois MNGStarRating et UIWebView sont les deux types d'éléments que je crée une contrainte entre, et ils 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 désirée qui servira de point d'inflexion. On pourrait aussi devenir beaucoup plus compliqué mais le filtre fait du bon boulot ici.

Animation de contraintes à l'aide de Swift

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

En supposant que vous créez une propriété à filtrer avec des critères précis et que vous atteignez un point d'inflexion spécifique pour votre animation (bien sûr, vous pouvez également filtrer pour un tableau et faire une boucle 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 tours

Ces notes sont vraiment un ensemble de conseils que j'ai écrits pour moi-même. J'ai fait tout ce que je n'ai pas fait personnellement et douloureusement. J'espère que ce guide peut épargner les autres.

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

  2. Prenez toujours une minute pour lire la documentation (nouvelle et ancienne), l'aide rapide et les en-têtes. Apple continue de faire beaucoup de changements pour mieux gérer les contraintes AutoLayout (voir les vues de la pile). Ou au moins le livre de recettes AutoLayout . Gardez à l'esprit que parfois les meilleures solutions sont dans l'ancienne documentation / vidéos.

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

  4. Ne pas coder en dur des valeurs de mise en page spécifiques en tant que 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. CGRectContainsRect est un exemple

  5. Si besoin est, 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 par exemple
  6. Ne faites pas le 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. Changez vos priorités pour toutes les contraintes à moins de 1000. J'ai mis le mien à 250 (faible) ou 750 (élevé) sur le storyboard; (Si vous essayez de changer une priorité de 1000 à quelque chose dans le code, l'application va planter, car 1000 est nécessaire)
  8. N'essayez pas immédiatement d'utiliser activateConstraints et deactivateConstraints (ils ont leur place mais quand vous apprenez ou si vous utilisez un storyboard en utilisant cela signifie probablement que vous en faites trop, ils ont une place bien que vu ci-dessous)
  9. Envisagez de ne pas utiliser addConstraints / removeConstraints sauf si vous ajoutez réellement une nouvelle contrainte dans le code. J'ai trouvé que la plupart du temps je plaçais les vues dans le storyboard avec les contraintes désirées (en plaçant la vue hors écran), puis en code, j'animais les contraintes précédemment créées dans le storyboard pour déplacer la vue.
  10. J'ai passé beaucoup de temps à construire des contraintes avec la nouvelle classe NSAnchorLayout et ses sous-classes. Cela fonctionne 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 générez des contraintes dans le code, utilisez certainement cette méthode pour agréger vos contraintes:

Exemple rapide de solutions à éviter lorsque vous utilisez des 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 vous oubliez l'un de ces conseils ou les plus simples tels que l'endroit où ajouter la layoutIfNeeded, très probablement rien ne se passera: Dans ce cas, vous pouvez avoir une solution à moitié cuite comme ceci:

NB - Prenez un moment pour lire la section AutoLayout ci-dessous et le guide d'origine. 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 second extrait est pour utiliser OS X). BTW - Ce n'est plus dans le guide actuel autant que je peux voir. Les techniques préférées continuent d'évoluer.

Animation des modifications apportées par la disposition 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 modifier vos contraintes par programmation. Le concept de base est le même pour iOS et OS X, mais il y a 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 lors de l'utilisation d'animations avec couches:

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

Lorsque vous n'utilisez pas d'animations avec couches, vous devez animer la constante à l'aide de l'animateur de contrainte:

[[constraint animator] setConstant:42];

Pour ceux qui apprennent mieux visuellement vérifier cette vidéo précoce d'Apple .

Porter une attention particulière

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

Bonne chance et que la force soit avec toi.

Je suis en AdBannerView mettre à jour une ancienne application avec un AdBannerView et lorsqu'il n'y a pas d'annonce, elle glisse hors de l'écran. Quand il y a une publicité, elle glisse sur l'écran. Trucs de base.

Ancien style, j'ai mis 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 vue, 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 se déplace, exactement comme prévu, mais pas d'animation.

MISE À JOUR: J'ai regardé de nouveau la vidéo WWDC12 « 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 obtiens 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 de côté, j'ai vérifié plusieurs fois et cela est en cours d'exécution sur le thread principal.


Dans le contexte de l'animation par 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 d'un champ de texte au sommet du conteneur. Lors de l'ouverture du clavier, je divise simplement la constante par 2.

Je n'ai pas réussi à obtenir une animation de contrainte lisse cohérente directement dans la notification du clavier. Environ la moitié des fois, la vue passerait juste à sa nouvelle position - sans animation.

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


Généralement, il suffit de mettre à jour les contraintes et d'appeler layoutIfNeeded dans le bloc d'animation. Cela peut être soit en changeant la propriété .constant d'un NSLayoutConstraint , en ajoutant des contraintes remove (iOS 7), soit en changeant la propriété .active des contraintes (iOS 8 & 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:

Controverse

Il y a quelques 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 les constantes de changement en dehors du bloc d'animation peuvent actuellement fonctionner, ce n'est pas sûr et elles devraient vraiment être modifiées dans 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ée. Il utilise Objective C et anime la vue en changeant la propriété .active de plusieurs contraintes. https://github.com/shepting/SampleAutoLayoutAnimation


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.


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

Les autres réponses sont totalement vraies: vous devez appeler [self.view layoutIfNeeded]; dans animateWithDuration: animations: Cependant, l'autre point important est d'avoir des pointeurs pour chaque NSLayoutConstraint vous voulez animer.

J'ai créé un exemple dans GitHub .


Solution de travail 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érée à un customView (CalendarView). J'ai appelé .layoutIfNeeded () sur self.view et PAS sur self.calendarView

J'espère que cette aide.


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é dans toutes mes applications pour les animer correctement. Certaines solutions ne fonctionnent pas ici, vous devriez les vérifier sur les appareils plus lents, par exemple l'iPhone 5.

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.
}

Swift 4 solution

UIView.animate

Trois étapes simples:

  1. Changer les contraintes, par exemple:

    heightAnchor.constant = 50
    
  2. Dites à la view contenant que sa mise en page est sale et que la mise en page automatique doit recalculer la mise en page:

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

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

Exemple le plus simple:

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

Sidenote

Il y a une 0ème étape optionnelle - avant de changer les contraintes, vous pouvez 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 cela ne devrait 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

Depuis iOS 10, nous avons un nouveau mécanisme d'animation - UIViewPropertyAnimator , nous devrions savoir que le même mécanisme s'applique fondamentalement. 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()

Puisque l' animator est une encapsulation de l'animation, nous pouvons garder une référence et l'appeler plus tard. Cependant, puisque dans le bloc d'animation, nous disons simplement à l'autolayout de recalculer les images, 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 modification des contraintes et le démarrage d'un animateur sont importants - si nous changeons simplement les contraintes et laissons notre animateur à un point ultérieur, le cycle de rafraîchissement suivant peut invoquer le recalcul autolayout et le changement ne sera pas animé.

Souvenez-vous également qu'un seul animateur n'est pas réutilisable - une fois que vous l'avez exécuté, 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 on l'utilise pour contrôler une animation interactive.







autolayout