Frage Wie animiere ich Constraint-Änderungen?


Ich aktualisiere eine alte App mit einem AdBannerView Wenn keine Anzeige vorhanden ist, wird sie vom Bildschirm verschoben. Wenn eine Anzeige geschaltet wird, wird sie auf dem Bildschirm angezeigt. Grundlegende Sachen.

Alter Stil, ich habe den Rahmen in einem Animationsblock gesetzt. Neuer Stil, ich habe eine IBOutlet zu der Beschränkung, die die Y-Position bestimmt, in diesem Fall ist es die Entfernung von der Unterseite der Superansicht, und modifiziere 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.

AKTUALISIEREN: Ich habe WWDC12-Video erneut angeschaut. "Best Practices für das Mastering von Auto-Layout"Dies deckt die Animation ab. Es wird erläutert, wie Sie Constraints mit CoreAnimation.

enter image description here enter image description here

Ich habe es mit folgendem Code versucht, bekomme 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 das mehrfach überprüft und dies wird am Hauptthread ausgeführt.


832
2017-09-27 13:23


Ursprung


Antworten:


Zwei wichtige Hinweise:

  1. Sie müssen anrufen layoutIfNeeded innerhalb des Animationsblocks. Apple empfiehlt, dass Sie es vor dem Animationsblock einmal aufrufen, um sicherzustellen, dass alle ausstehenden Layoutoperationen abgeschlossen wurden

  2. Sie müssen es speziell auf der Elternansicht (z.B. self.view), nicht die untergeordnete Ansicht, an die die Abhängigkeiten gebunden sind. Dies wird aktualisiert alle eingeschränkte Ansichten, 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 den oberen Versatz von Ansicht A geändert und möchten, dass Ansicht B damit animiert wird)

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

Schnell 3

UIView.animate(withDuration: 5) {
    self._addBannerDistanceFromBottomConstraint.constant = 0
    self.view.layoutIfNeeded()
}

1505
2017-09-30 19:00



Ich schätze die gegebene Antwort, aber ich denke, es wäre schön, noch etwas weiter zu gehen.

Die grundlegende Blockanimation aus der Dokumentation

[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 simples Szenario. Was passiert, wenn ich Subview-Constraints über die updateConstraints Methode?

Ein Animationsblock, der die Subviews updateConstraints-Methode aufruft

[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 außer Kraft gesetzt und muss am Ende der Methode super aufgerufen werden.

- (void)updateConstraints
{
    // Update some constraints

    [super updateConstraints];
}

Das AutoLayout-Handbuch lässt viel zu wünschen übrig, aber es lohnt sich zu lesen. Ich selbst benutze das als Teil eines UISwitch das schaltet eine Unteransicht mit einem Paar von UITextFields mit einer einfachen und subtilen Kollaps-Animation (0,2 Sekunden lang). Die Einschränkungen für die Untersicht werden wie oben beschrieben in den updateConstraints-Methoden der UIView-Unterklassen behandelt.


96
2018-01-22 00:35



Im Allgemeinen müssen Sie nur Einschränkungen aktualisieren und aufrufen layoutIfNeeded innerhalb des Animationsblocks. Dies kann entweder die ändern .constant Eigentum eines NSLayoutConstraintHinzufügen von Einschränkungen (iOS 7) oder Ändern der .active Eigenschaft der Einschränkungen (iOS 8 & 9).

Beispielcode:

[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];
}];

Beispieleinrichtung:

Xcode Project so sample animation project.

Kontroverse

Es gibt einige Fragen darüber, ob die Einschränkung geändert werden sollte Vor der Animationsblock, oder Innerhalb es (siehe vorherige Antworten).

Das Folgende ist eine Twitter-Konversation zwischen Martin Pilkington, der iOS unterrichtet, und Ken Ferry, der Auto-Layout geschrieben hat. Ken erklärt das, obwohl er Konstanten außerhalb des Animationsblocks ändert kann derzeit Arbeit, es ist nicht sicher und sie sollten sich wirklich ändern Innerhalb der Animationsblock. https://twitter.com/kongtomorrow/status/440627401018466305

Animation:

Beispielprojekt

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


58
2017-11-13 00:33



// 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];
}];

32
2017-12-23 14:29



Storyboard, Code, Tipps und ein paar Gotchas

Die anderen Antworten sind in Ordnung, aber dieses zeigt ein paar ziemlich wichtige Fälle von animierenden Constraints anhand eines aktuellen Beispiels. Ich habe viele Varianten durchgemacht, bevor ich folgendes realisiert habe:

Stellen Sie die Einschränkungen, auf die Sie abzielen möchten, in Klassenvariablen so ein, dass sie eine starke Referenz enthalten. In Swift habe ich träge Variablen benutzt:

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 einigen Experimenten habe ich festgestellt, dass man die Einschränkung aus der Sicht erhalten muss ÜBER (aka Superview) die beiden Ansichten, in denen die Einschränkung definiert ist. Im folgenden Beispiel (sowohl MNGStarRating als auch UIWebView sind die zwei Arten von Elementen, zwischen denen ich eine Einschränkung erstelle, und sie sind Subviews innerhalb von self.view).

Filterketten

Ich nutze Swifts Filtermethode, um die gewünschte Beschränkung zu trennen, die als Wendepunkt dienen wird. Man könnte auch viel komplizierter werden, aber filter macht hier einen guten Job.

Animieren von Einschränkungen mit Swift

Nota Bene - Dieses Beispiel ist die Storyboard / Code-Lösung und geht davon aus   man hat im Storyboard Standardbedingungen festgelegt. Das kann man dann   animiere die Änderungen mit Code.

Angenommen, Sie erstellen eine Eigenschaft zum Filtern mit genauen Kriterien und erhalten einen bestimmten Wendepunkt für Ihre Animation (Sie können natürlich auch nach einem Array filtern und eine Schleife durchlaufen, wenn Sie mehrere Integritätsregeln 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

        }
    }
}

Die vielen falschen Wendungen

Diese Notizen sind wirklich eine Reihe von Tipps, die ich für mich selbst geschrieben habe. Ich tat all die Verbote persönlich und schmerzhaft. Hoffentlich kann dieser Leitfaden andere ersparen.

  1. Achten Sie auf zPositioning. Manchmal, wenn scheinbar nichts ist passiert, sollten Sie einige der anderen Ansichten ausblenden oder die Ansicht verwenden Debugger, um Ihre animierte Ansicht zu finden. Ich habe sogar Fälle gefunden, wo eine benutzerdefinierte Runtime Das Attribut wurde in der XML-Datei eines Storyboards verloren und zum animierten animiert Ansicht wird abgedeckt (während der Arbeit).

  2. Nehmen Sie sich immer eine Minute Zeit, um die Dokumentation (neu und alt) zu lesen, Quick Hilfe und Kopfzeilen. Apple macht viele Änderungen besser Verwalten Sie AutoLayout-Einschränkungen (siehe Stapelansichten). Oder zumindest die AutoLayout Kochbuch. Beachten Sie, dass manchmal die besten Lösungen in der älteren Dokumentation / Videos sind.

  3. Spielen Sie mit den Werten in der Animation herum und ziehen Sie die Verwendung in Betracht andere AnimateWithDuration-Varianten.

  4. Spezifische Layoutwerte nicht als Kriterien für die Bestimmung fest codieren Änderungen an anderen Konstanten, stattdessen verwenden Sie Werte, die Ihnen erlauben Bestimmen Sie den Standort der Ansicht. CGRectContainsRectist ein Beispiel

  5. Zögern Sie bei Bedarf nicht, die Layoutgrenzen zu verwenden eine Ansicht, die an der Einschränkungsdefinition teilnimmt let viewMargins = self.webview.layoutMarginsGuide: ist ein Beispiel
  6. Machen Sie keine Arbeit, die Sie nicht tun müssen, alle Ansichten mit Einschränkungen für die Storyboard haben Einschränkungen an die Eigenschaft gebunden self.viewName.constraints
  7. Ändern Sie Ihre Prioritäten für alle Einschränkungen auf weniger als 1000. Ich setze Mine auf 250 (niedrig) oder 750 (hoch) auf dem Storyboard; (Wenn Sie versuchen, eine 1000-Priorität auf irgendeinen Code zu ändern, stürzt die App ab, weil 1000 benötigt wird)
  8. Bedenken Sie, dass Sie nicht sofort versuchen, activateConstraints und deactiveConstraints (sie haben ihren Platz, aber wenn du gerade lernst oder wenn du ein Storyboard verwendest, bedeutet das wahrscheinlich, dass du zu viel tust - sie haben aber einen Platz, wie unten zu sehen)
  9. Erwägen Sie nicht addConstraints / removeConstraints, wenn Sie es nicht sind wirklich eine neue Einschränkung im Code hinzufügen. Das habe ich meistens gefunden Legen Sie die Ansichten im Storyboard mit den gewünschten Einschränkungen (Platzieren) fest Im Code animiere ich die zuvor im Storyboard erstellten Einschränkungen, um die Ansicht zu verschieben.
  10. Ich habe viel Zeit damit verschwendet, Einschränkungen mit dem Neuen aufzubauen NSAnchorLayout Klasse und Unterklassen. Diese funktionieren gut, aber es dauerte eine Weile, um zu erkennen, dass alle Zwänge, die ich brauchte hat bereits im Storyboard existiert. Wenn Sie Einschränkungen im Code erstellen dann verwenden Sie diese Methode am sichersten, um Ihre Einschränkungen zu aggregieren:

Schnelles Beispiel von Lösungen zu vermeiden, wenn Sie Storyboards verwenden

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 Tipps vergessen oder die einfacheren, wie zum Beispiel das Hinzufügen von layoutIfNeeded, passiert höchstwahrscheinlich nichts: In diesem Fall haben Sie vielleicht eine halbgebackene Lösung wie folgt:

NB - Nehmen Sie sich einen Moment Zeit, um den AutoLayout - Abschnitt unten und den   Originalführer. Es gibt eine Möglichkeit, diese Techniken zu ergänzen   deine dynamischen Animatoren.

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 OS X verwendet wird). BTW - Dies ist nicht mehr in der aktuellen Anleitung, soweit ich sehen kann.  Die bevorzugten Techniken entwickeln sich weiter.

Animieren von Änderungen, die von Auto Layout vorgenommen wurden

Wenn Sie vollständige Kontrolle über das Animieren von Änderungen benötigen, die von Auto Layout vorgenommen werden, müssen Sie Ihre Abhängigkeit programmgesteuert ändern. Das Grundkonzept ist für iOS und OS X gleich, aber es gibt ein paar kleine Unterschiede.

In einer iOS-App würde Ihr Code etwa wie folgt 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 layergestützte Animationen verwenden:

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

Wenn Sie keine Animationen mit Ebenenunterstützung verwenden, müssen Sie die Konstante mithilfe des Animators der Integritätsbedingung animieren:

[[constraint animator] setConstant:42];

Für diejenigen, die besser visuell lernen, sehen Sie sich das früh an Video von Apple.

Pass gut auf

Oft gibt es in der Dokumentation kleine Notizen oder Codeabschnitte, die zu größeren Ideen führen. Zum Beispiel ist das Anhängen von automatischen Layoutbeschränkungen an dynamische Animatoren eine große Idee.

Viel Glück und möge die Macht mit dir sein.


10
2017-12-11 01:49



Schnelle 4 Lösung

UIView.animate

Drei einfache Schritte:

  1. Ändern Sie die Einschränkungen, z.

    heightAnchor.constant = 50
    
  2. Erzählen Sie den Inhalt view dass sein Layout schmutzig ist und das Layout automatisch das Layout neu berechnet:

    self.view.setNeedsLayout()
    
  3. Im Animationsblock teilen Sie dem Layout mit, das Layout neu zu berechnen, was der Einstellung der Frames direkt entspricht (in diesem Fall wird das automatische Layout die Frames festlegen):

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

Das einfachste Beispiel:

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

Randnotiz

Es gibt einen optionalen 0. Schritt - bevor Sie die Einschränkungen ändern, die Sie möglicherweise aufrufen möchten self.view.layoutIfNeeded() um sicherzustellen, dass der Startpunkt für die Animation aus dem Zustand stammt, in dem alte Einschränkungen angewendet wurden (falls einige andere Einschränkungen Änderungen enthielten, die nicht in der Animation enthalten sein sollten):

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

Seit iOS 10 haben wir einen neuen Animationsmechanismus - UIViewPropertyAnimatorWir sollten wissen, dass im Grunde derselbe Mechanismus auf ihn zutrifft. Die Schritte sind im Grunde die gleichen:

heightAnchor.constant = 50
self.view.setNeedsLayout()
let animator = UIViewPropertyAnimator(duration: 0.5, timingParameters: UICubicTimingParameters(animationCurve: .linear))
animator.addAnimations {
    self.view.layoutIfNeeded()
}
animator.startAnimation()

Schon seit animator ist eine Verkapselung der Animation, wir können darauf Bezug nehmen und sie später aufrufen. Da wir jedoch in dem Animationsblock dem automatischen Layout nur mitteilen, dass die Frames neu berechnet werden sollen, müssen wir vor dem Aufruf die Bedingungen ändern startAnimation. 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 der Änderung von Abhängigkeiten und das Starten eines Animators ist wichtig - wenn wir nur die Abhängigkeiten ändern und unseren Animator für einen späteren Zeitpunkt verlassen, kann der nächste Neuzeichnungszyklus die Neuberechnung des automatischen Layouts aufrufen, und die Änderung wird nicht animiert.

Denken Sie auch daran, dass ein einzelner Animator nicht wiederverwendbar ist. Wenn Sie ihn einmal ausführen, können Sie ihn nicht erneut ausführen. Ich denke also, dass es keinen guten Grund gibt, den Animator zu behalten, es sei denn, wir benutzen ihn zur Steuerung einer interaktiven Animation.


7
2018-02-16 11:09



Schnelle Lösung:

yourConstraint.constant = 50
UIView.animateWithDuration(1, animations: yourView.layoutIfNeeded)

6
2017-08-11 08:42