Ich aktualisiere eine alte App mit AdBannerView
, und wenn keine Anzeige vorhanden ist, wird sie vom Bildschirm verschoben. Wenn es eine Anzeige gibt, wird sie auf dem Bildschirm angezeigt. Grundlegendes Zeug.
Im alten Stil habe ich den Frame in einen Animationsblock gesetzt. Neuer Stil, ich habe eine IBOutlet
für die Auto-Layout-Einschränkung, die die Y-Position bestimmt, in diesem Fall den Abstand vom unteren Rand der Übersicht, und ändere die Konstante:
- (void)moveBannerOffScreen {
[UIView animateWithDuration:5 animations:^{
_addBannerDistanceFromBottomConstraint.constant = -32;
}];
bannerIsVisible = FALSE;
}
- (void)moveBannerOnScreen {
[UIView animateWithDuration:5 animations:^{
_addBannerDistanceFromBottomConstraint.constant = 0;
}];
bannerIsVisible = TRUE;
}
Und das Banner bewegt sich genau wie erwartet, aber keine Animation.
UPDATE: Ich habe es mir noch einmal angeschaut WWDC 12 - Best Practices für die Beherrschung des automatischen Layouts welches die Animation abdeckt. Es wird erläutert, wie Einschränkungen mit CoreAnimation aktualisiert werden:
Ich habe es mit dem folgenden Code versucht, erhalte aber genau die gleichen Ergebnisse:
- (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;
}
Nebenbei habe ich mehrfach nachgesehen und dies wird auf dem Haupt-Thread ausgeführt.
Zwei wichtige Hinweise:
Sie müssen layoutIfNeeded
innerhalb des Animationsblocks aufrufen. Apple empfiehlt, es einmal vor dem Animationsblock aufzurufen, um sicherzustellen, dass alle ausstehenden Layoutvorgänge abgeschlossen wurden
Sie müssen es speziell für die übergeordnete Ansicht (z. B. self.view
) aufrufen, nicht für die untergeordnete Ansicht, mit der die Einschränkungen verknüpft sind. Dadurch werden alle eingeschränkte Ansichten aktualisiert, einschließlich der Animation anderer Ansichten, die möglicherweise auf die Ansicht beschränkt sind, deren Einschränkung Sie geändert haben (z. B. Ansicht B ist am unteren Rand von Ansicht A angefügt und Sie haben gerade die Ansicht A geändert) oben versetzt und Sie möchten Ansicht B damit animieren)
Versuche dies:
Ziel-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
UIView.animate(withDuration: 5) {
self._addBannerDistanceFromBottomConstraint.constant = 0
self.view.layoutIfNeeded()
}
Ich weiß die Antwort zu schätzen, aber ich denke, es wäre schön, etwas weiter zu gehen.
[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
}];
aber das ist wirklich ein sehr vereinfachtes Szenario. Was ist, wenn ich Unteransichtsbeschränkungen mit der updateConstraints
-Methode animieren möchte?
[self.view layoutIfNeeded];
[self.subView setNeedsUpdateConstraints];
[self.subView updateConstraintsIfNeeded];
[UIView animateWithDuration:1.0f delay:0.0f options:UIViewAnimationOptionLayoutSubviews animations:^{
[self.view layoutIfNeeded];
} completion:nil];
Die updateConstraints-Methode wird in der UIView-Unterklasse überschrieben und muss am Ende der Methode super aufrufen.
- (void)updateConstraints
{
// Update some constraints
[super updateConstraints];
}
The AutoLayout Guide lässt zu wünschen übrig, ist aber lesenswert. Ich selbst benutze dies als Teil einer UISwitch
, die eine Unteransicht mit einem Paar UITextField
mit einer einfachen und subtilen Zusammenbruchanimation (0,2 Sekunden lang) umschaltet. Die Einschränkungen für die Unteransicht werden in den Methoden updateConstraints der UIView-Unterklassen wie oben beschrieben behandelt.
Im Allgemeinen müssen Sie nur die Einschränkungen aktualisieren und layoutIfNeeded
im Animationsblock aufrufen. Dies kann entweder das Ändern der .constant
-Eigenschaft eines NSLayoutConstraint
, das Hinzufügen von Einschränkungen (iOS 7) oder das Ändern der .active
-Eigenschaft von Einschränkungen (iOS 8 und 9) sein.
[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];
}];
Es gibt einige Fragen, ob die Einschränkung geändert werden soll vor dem Animationsblock oder innerhalb it (siehe vorherige Antworten).
Das Folgende ist ein Twitter-Gespräch zwischen Martin Pilkington, der iOS unterrichtet, und Ken Ferry, der Auto Layout geschrieben hat. Ken erklärt, dass das Ändern von Konstanten außerhalb des Animationsblocks kann derzeit funktionieren, aber nicht sicher ist und dass sie wirklich geändert werden sollten inside = der Animationsblock. https://Twitter.com/kongtomorrow/status/440627401018466305
Hier ist ein einfaches Projekt, das zeigt, wie eine Ansicht animiert werden kann. Es verwendet Objective C und animiert die Ansicht durch Ändern der .active
-Eigenschaft mehrerer Einschränkungen. 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];
}];
Swift 4 Lösung
Drei einfache Schritte:
Ändern Sie die Einschränkungen, z.
heightAnchor.constant = 50
Sagen Sie dem enthaltenen view
, dass sein Layout verschmutzt ist und dass das Autolayout das Layout neu berechnen soll:
self.view.setNeedsLayout()
Im Animationsblock weisen Sie das Layout an, das Layout neu zu berechnen. Dies entspricht dem direkten Festlegen der Frames (in diesem Fall werden die Frames durch das automatische Layout festgelegt):
UIView.animate(withDuration: 0.5) {
self.view.layoutIfNeeded()
}
Komplettes einfachstes Beispiel:
heightAnchor.constant = 50
self.view.setNeedsLayout()
UIView.animate(withDuration: 0.5) {
self.view.layoutIfNeeded()
}
Nebenbemerkung
Es gibt einen optionalen 0. Schritt - bevor Sie die Einschränkungen ändern, können Sie self.view.layoutIfNeeded()
aufrufen, um sicherzustellen, dass der Startpunkt für die Animation aus dem Zustand stammt, in dem alte Einschränkungen angewendet wurden (falls sich einige andere Einschränkungen geändert haben sollten) nicht in der Animation enthalten sein):
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()
}
Da wir mit iOS 10 einen neuen Animationsmechanismus haben - UIViewPropertyAnimator
, sollten wir wissen, dass im Grunde derselbe Mechanismus für ihn gilt. Die Schritte sind grundsätzlich gleich:
heightAnchor.constant = 50
self.view.setNeedsLayout()
let animator = UIViewPropertyAnimator(duration: 0.5, timingParameters: UICubicTimingParameters(animationCurve: .linear))
animator.addAnimations {
self.view.layoutIfNeeded()
}
animator.startAnimation()
Da animator
eine Kapselung der Animation ist, können wir auf sie verweisen und sie später aufrufen. Da wir im Animationsblock nur das automatische Layout anweisen, die Frames neu zu berechnen, müssen wir die Einschränkungen ändern, bevor wir startAnimation
aufrufen. Daher ist so etwas möglich:
// 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()
Die Reihenfolge, in der Abhängigkeiten geändert und ein Animator gestartet werden, ist wichtig. Wenn Sie lediglich die Abhängigkeiten ändern und den Animator für einen späteren Zeitpunkt verlassen, kann der nächste Neuzeichnungszyklus eine automatische Neuberechnung des Layouts aufrufen und die Änderung wird nicht animiert.
Denken Sie auch daran, dass ein einzelner Animator nicht wiederverwendbar ist. Wenn Sie ihn einmal ausgeführt haben, können Sie ihn nicht erneut ausführen. Es gibt also wohl keinen guten Grund, den Animator in der Nähe zu halten, es sei denn, wir verwenden ihn zur Steuerung einer interaktiven Animation.
Die anderen Antworten sind in Ordnung, aber diese hebt anhand eines aktuellen Beispiels einige ziemlich wichtige Fallstricke bei der Animation von Einschränkungen hervor. Ich habe viele Variationen durchlaufen, bevor mir Folgendes klar wurde:
Machen Sie die Einschränkungen, auf die Sie abzielen möchten, zu Klassenvariablen, um eine starke Referenz zu halten. In Swift habe ich faule Variablen verwendet:
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!
}()
Nach einigem Experimentieren stellte ich fest, dass man die Einschränkung aus der Ansicht ÜBER (auch bekannt als die Übersicht) der beiden Ansichten, in denen die Einschränkung definiert ist, erhalten MUSS. Im folgenden Beispiel (sowohl MNGStarRating als auch UIWebView sind die beiden Arten von Elementen, zwischen denen ich eine Einschränkung erstelle, und sie sind Unteransichten in self.view).
Filterverkettung
Ich nutze Swifts Filtermethode, um die gewünschte Einschränkung zu trennen, die als Wendepunkt dient. Man könnte auch viel komplizierter werden, aber Filter macht hier einen guten Job.
Animieren von Constraints mit Swift
Nota Bene - Dieses Beispiel ist die Storyboard/Code-Lösung und setzt voraus, dass im Storyboard Standardeinschränkungen festgelegt wurden. Die Änderungen können dann mit Code animiert werden.
Angenommen, Sie erstellen eine Eigenschaft, um nach genauen Kriterien zu filtern und zu einem bestimmten Wendepunkt für Ihre Animation zu gelangen (natürlich können Sie auch nach einem Array filtern und eine Schleife durchlaufen, wenn Sie mehrere Einschränkungen benötigen):
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!
}()
....
Etwas später...
@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
}
}
}
Diese Notizen sind wirklich eine Reihe von Tipps, die ich für mich selbst geschrieben habe. Ich habe alle Verbote persönlich und schmerzhaft ausgeführt. Hoffentlich kann dieser Leitfaden andere verschonen.
Achten Sie auf zPositioning. Manchmal, wenn anscheinend nichts passiert, sollten Sie einige der anderen Ansichten ausblenden oder den Ansichtsdebugger verwenden, um Ihre animierte Ansicht zu finden. Ich habe sogar Fälle gefunden, in denen ein benutzerdefiniertes Laufzeitattribut in der XML eines Storyboards verloren ging und dazu führte, dass die animierte Ansicht (während der Arbeit) abgedeckt wurde.
Nehmen Sie sich immer eine Minute Zeit, um die Dokumentation (neu und alt), die Schnellhilfe und die Überschriften zu lesen. Apple nimmt immer wieder Änderungen vor, um die AutoLayout-Einschränkungen besser zu verwalten (siehe Stapelansichten). Oder zumindest das AutoLayout Cookbook . Denken Sie daran, dass die älteren Dokumentationen/Videos manchmal die besten Lösungen bieten.
Spielen Sie mit den Werten in der Animation und erwägen Sie, andere animateWithDuration-Varianten zu verwenden.
Codieren Sie bestimmte Layoutwerte nicht fest als Kriterien zum Bestimmen von Änderungen an anderen Konstanten. Verwenden Sie stattdessen Werte, mit denen Sie die Position der Ansicht bestimmen können. CGRectContainsRect
ist ein Beispiel
let viewMargins = self.webview.layoutMarginsGuide
: beteiligt istSchnelle Auswahl von Lösungen, die bei der Verwendung von Storyboards ZU VERMEIDEN sind
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
}()
Wenn Sie einen dieser oder einfachere Tipps vergessen, z. B. wo das layoutIfNeeded hinzugefügt werden soll, passiert höchstwahrscheinlich nichts: In diesem Fall haben Sie möglicherweise eine halbherbe Lösung wie die folgende:
NB - Nehmen Sie sich einen Moment Zeit, um den folgenden Abschnitt zum automatischen Layout und die Originalanleitung zu lesen. Es gibt eine Möglichkeit, diese Techniken zu verwenden, um Ihre dynamischen Animatoren zu ergänzen.
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
}
}
Snippet aus dem AutoLayout-Handbuch (beachten Sie, dass das zweite Snippet für die Verwendung von OS X vorgesehen ist). Übrigens - Soweit ich sehen kann, ist dies nicht mehr im aktuellen Handbuch enthalten. Die bevorzugten Techniken entwickeln sich weiter.
Animieren von Änderungen, die durch das automatische Layout vorgenommen wurden
Wenn Sie die volle Kontrolle über das Animieren von Änderungen benötigen, die von Auto Layout vorgenommen werden, müssen Sie die Änderungen an den Einschränkungen programmgesteuert vornehmen. Das Grundkonzept ist für iOS und OS X gleich, es gibt jedoch einige geringfügige Unterschiede.
In einer iOS-App würde Ihr Code ungefähr so aussehen:
[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
}];
Verwenden Sie in OS X den folgenden Code, wenn Sie Animationen mit Ebenen verwenden:
[containterView layoutSubtreeIfNeeded];
[NSAnimationContext runAnimationGroup:^(NSAnimationContext *context) {
[context setAllowsImplicitAnimation: YES];
// Make all constraint changes here
[containerView layoutSubtreeIfNeeded];
}];
Wenn Sie keine Ebenenanimationen verwenden, müssen Sie die Konstante mit dem Animator der Einschränkung animieren:
[[constraint animator] setConstant:42];
Für diejenigen, die besser visuell lernen, sehen Sie sich dies früh an Video von Apple .
Oft gibt es in der Dokumentation kleine Notizen oder Code-Teile, die zu größeren Ideen führen. Zum Beispiel ist es eine gute Idee, dynamische Animatoren mit Auto-Layout-Einschränkungen zu versehen.
Viel Glück und möge die Macht mit dir sein.
Schnelle Lösung:
yourConstraint.constant = 50
UIView.animate(withDuration: 1.0, animations: {
yourView.layoutIfNeeded
})
Arbeitslösung 100% Swift 3.1
ich habe alle Antworten gelesen und möchte den Code und die Hierarchie der Zeilen teilen, die ich in all meinen Anwendungen verwendet habe, um sie korrekt zu animieren. Einige Lösungen funktionieren hier nicht. Sie sollten sie in diesem Moment auf langsameren Geräten, z. B. iPhone 5, überprüfen.
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.
}
Ich habe versucht, Constraints zu animieren, und es war nicht einfach, eine gute Erklärung zu finden.
Was andere Antworten aussagen, ist völlig richtig: Sie müssen [self.view layoutIfNeeded];
innerhalb von animateWithDuration: animations:
aufrufen. Der andere wichtige Punkt ist jedoch, Zeiger für jedes NSLayoutConstraint
zu haben, das Sie animieren möchten.
Funktionierende und gerade getestete Lösung für Swift 3 mit 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)
Denken Sie daran, dass self.calendarViewHeight eine Einschränkung für eine benutzerdefinierte Ansicht (CalendarView) ist. Ich habe .layoutIfNeeded () bei self.view und NICHT bei self.calendarView aufgerufen
Ich hoffe das hilft.
Es gibt einen Artikel darüber: http://weblog.invasivecode.com/post/42362079291/auto-layout-and-core-animation-auto-layout-was
In dem codierte er wie folgt:
- (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];
}];
}
}
Ich hoffe es hilft.
Im Kontext der Einschränkungsanimation möchte ich eine bestimmte Situation erwähnen, in der ich eine Einschränkung sofort innerhalb einer Benachrichtigung mit geöffneter Tastatur animiert habe.
Die Einschränkung definierte einen oberen Bereich von einem Textfeld bis zum oberen Rand des Containers. Beim Öffnen der Tastatur dividiere ich die Konstante einfach durch 2.
Es ist mir nicht gelungen, eine konsistente Animation mit weichen Einschränkungen direkt in der Tastaturbenachrichtigung zu erzielen. In etwa der Hälfte der Fälle würde die Ansicht einfach an ihre neue Position springen - ohne zu animieren.
Mir ist aufgefallen, dass aufgrund des Öffnens der Tastatur möglicherweise zusätzliche Layouts erstellt werden. Durch Hinzufügen eines einfachen dispatch_after-Blocks mit einer Verzögerung von 10 ms wurde die Animation jedes Mal ausgeführt - kein Springen.