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.
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).
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.
Jouez avec les valeurs de l'animation et envisagez d'utiliser d'autres variantes animateWithDuration.
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. CGRectContainsRect
est un exemple
- 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
- 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
- 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)
- 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)
- 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.
- 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.