Wie animiere ich Einschränkungsänderungen?

959

Ich aktualisiere eine alte App mit einer AdBannerViewund wenn keine Anzeige vorhanden ist, wird sie vom Bildschirm verschoben. Wenn eine Anzeige vorhanden ist, wird sie auf dem Bildschirm angezeigt. Grundlegende Sachen.

Im alten Stil habe ich den Rahmen in einen Animationsblock gesetzt. Neuer Stil, ich habe eine IBOutletEinschränkung für das automatische Layout, die die YPosition 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 WWDC 12 erneut gesehen, wie Best Practices für das Beherrschen des automatischen Layouts, das sich mit Animation befasst, besprochen wurden . Es wird erläutert, wie Einschränkungen mithilfe von 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 bemerkt, ich habe mehrfach überprüft und dies wird auf dem Haupt- Thread ausgeführt.

DBD
quelle
11
Ich habe noch nie zuvor so viele Stimmen für eine Frage und Antwort auf einen Tippfehler auf SO gesehen
Abbood
3
Wenn die Antwort einen Tippfehler enthält, sollten Sie die Antwort bearbeiten. Deshalb können sie bearbeitet werden.
i_am_jorf
@ Jeffamaphone - Es wäre nützlicher, wenn Sie auf den Tippfehler hinweisen würden, damit ich weiß, wo der Fehler liegt. Sie können die Antwort selbst bearbeiten und den Tippfehler korrigieren, um allen anderen unsere Schande zu ersparen. Ich habe es nur bearbeitet, um die Konstante aus dem Animationsblock zu entfernen, wenn Sie sich darauf bezogen haben.
DBD
1
Ich weiß nicht, was der Tippfehler ist. Ich habe auf die obigen Kommentare geantwortet.
i_am_jorf
9
Dann wird der Tippfehler ist die Frage. Dummerweise habe ich "setNeedsLayout" anstelle von "layoutIfNeeded" eingegeben. In meiner Frage wird deutlich, wenn ich meinen Code mit dem Fehler und die Screenshots mit dem richtigen Befehl ausschneide und einfüge. Ich konnte es jedoch nicht bemerken, bis jemand darauf hinwies.
DBD

Antworten:

1696

Zwei wichtige Hinweise:

  1. Sie müssen layoutIfNeededinnerhalb des Animationsblocks aufrufen . Apple empfiehlt, dass Sie es einmal vor dem Animationsblock aufrufen, um sicherzustellen, dass alle ausstehenden Layoutvorgänge abgeschlossen sind

  2. Sie müssen es speziell in der übergeordneten Ansicht (z. B. self.view) aufrufen , nicht in der untergeordneten Ansicht, mit der die Einschränkungen verknüpft sind. Dadurch werden alle eingeschränkten 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. wird Ansicht B am unteren Rand von Ansicht A angehängt, und Sie haben gerade den oberen Versatz von Ansicht A geändert, 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 3

UIView.animate(withDuration: 5) {
    self._addBannerDistanceFromBottomConstraint.constant = 0
    self.view.layoutIfNeeded()
}
g3rv4
quelle
199
Sie wissen was ... Ihre Antwort funktioniert. Das WWDC funktioniert ... meine Vision versagt. Aus irgendeinem Grund brauchte ich eine Woche, um zu erkennen, dass ich anrief setNeedsLayoutanstatt layoutIfNeeded. Ich bin etwas entsetzt darüber, wie viele Stunden ich damit verbracht habe, nicht zu bemerken, dass ich nur den falschen Methodennamen eingegeben habe.
DBD
23
Die Lösung funktioniert, aber Sie müssen die Einschränkungskonstante im Animationsblock nicht ändern . Es ist völlig in Ordnung, die Einschränkung einmal festzulegen, bevor die Animation gestartet wird. Sie sollten Ihre Antwort bearbeiten.
Ortwin Gentz
74
Dies funktionierte anfangs nicht für mich und dann wurde mir klar, dass Sie layoutIfNeeded in der PARENT-Ansicht aufrufen müssen, nicht in der Ansicht, für die die Einschränkungen gelten.
Oliver Pearmain
20
Mit layoutIfNeeded werden alle Aktualisierungen der Unteransicht animiert, nicht nur die Änderung der Einschränkung. Wie animierst du nur die Einschränkungsänderung?
ngb
11
"Apple empfiehlt, dass Sie es einmal vor dem Animationsblock aufrufen, um sicherzustellen, dass alle ausstehenden Layoutvorgänge abgeschlossen sind", danke, habe nie darüber nachgedacht, aber es ist sinnvoll.
Rick van der Linde
109

Ich freue mich über die Antwort, aber ich denke, es wäre schön, noch ein bisschen 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 vereinfachtes Szenario. Was ist, wenn ich Subview-Einschränkungen über die updateConstraintsMethode animieren möchte ?

Ein Animationsblock, der die Methode updateConstraints der Unteransichten 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 überschrieben und muss am Ende der Methode super aufrufen.

- (void)updateConstraints
{
    // Update some constraints

    [super updateConstraints];
}

Das AutoLayout-Handbuch lässt zu wünschen übrig, ist aber lesenswert. Ich selbst verwende dies als Teil einer UISwitch, die eine Unteransicht mit einem Paar UITextFields mit einer einfachen und subtilen Kollapsanimation (0,2 Sekunden lang) umschaltet . Die Einschränkungen für die Unteransicht werden in den UpdateConstraints-Methoden der UIView-Unterklassen wie oben beschrieben behandelt.

Cameron Lowell Palmer
quelle
Wenn Sie alle oben genannten Methoden aufrufen self.view(und nicht in einer Unteransicht dieser Ansicht), ist ein Aufruf updateConstraintsIfNeedednicht erforderlich (da setNeedsLayoutauch updateConstraintsdiese Ansicht ausgelöst wird ). Könnte für die meisten trivial sein, aber es war bis jetzt nicht für mich;)
Anneblue
Es ist schwer zu kommentieren, ohne das vollständige Zusammenspiel von Autolayout und Feder- und Federbeinlayout in Ihrem speziellen Fall zu kennen. Ich spreche ausschließlich von einem reinen Autolayout-Szenario. Ich wäre gespannt, was genau in Ihrer layoutSubviews-Methode passiert.
Cameron Lowell Palmer
translatesAutoresizingMaskIntoConstraints = NO; Wenn Sie das reine Autolayout durchführen möchten, sollten Sie dies auf jeden Fall deaktivieren.
Cameron Lowell Palmer
Ich habe das nicht gesehen, aber ich kann ohne Code nicht wirklich mit dem Problem sprechen. Vielleicht könnten Sie eine TableView verspotten und auf GitHub kleben?
Cameron Lowell Palmer
Entschuldigung, ich benutze nicht gitHub :(
anneblue
74

Im Allgemeinen müssen Sie nur die Einschränkungen aktualisieren und layoutIfNeededden Animationsblock aufrufen . Dies kann entweder das Ändern der .constantEigenschaft von NSLayoutConstraint, das Hinzufügen von Einschränkungen zum Entfernen (iOS 7) oder das Ändern der .activeEigenschaft von Einschränkungen (iOS 8 und 9) sein.

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

Beispiel-Setup:

Xcode-Projekt, also Beispiel für ein Animationsprojekt.

Kontroverse

Es gibt einige Fragen dazu, ob die Einschränkung vor oder innerhalb des Animationsblocks geändert werden soll (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 derzeit möglicherweise funktioniert, jedoch nicht sicher ist und dass sie wirklich innerhalb des Animationsblocks geändert werden sollten . 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, indem die .activeEigenschaft mehrerer Einschränkungen geändert wird. https://github.com/shepting/SampleAutoLayoutAnimation

Steven Hepting
quelle
+1 für die Darstellung eines Beispiels mit dem neuen aktiven Flag. Außerdem fühlte sich das Ändern der Einschränkung außerhalb des Animationsblocks für mich immer wie ein Hack an
Korey Hinton,
Ich habe ein Problem in Github angesprochen, weil diese Lösung nicht funktioniert, um die Ansicht wieder dorthin zu verschieben, wo sie war. Ich glaube, ein aktiver Wechsel ist nicht die richtige Lösung, und Sie sollten stattdessen die Priorität ändern. Setzen Sie die Oberseite auf 750 und die Unterseite auf 250, und wechseln Sie dann im Code zwischen UILayoutPriorityDefaultHigh und UILayoutPriorityDefaultLow.
Malhal
Ein weiterer Grund für die Aktualisierung innerhalb des Blocks besteht darin, dass die Änderungen vom aufrufenden Code isoliert werden, der möglicherweise auch beim Aufruf von layoutIfNeeded auftritt. (und danke für den Twitter-Link)
Chris Conover
1
Gute Antwort. Vor allem, weil Sie Martins und Kens Gespräch darüber erwähnen.
Jona
Ich bin verwirrt über die Art und Weise, wie Einschränkungen aktualisiert werden, und habe dies geschrieben . Können Sie bitte einen Blick darauf werfen?
Honig
36
// 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];
}];
John Erck
quelle
29

Swift 4-Lösung

UIView.animate

Drei einfache Schritte:

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

    heightAnchor.constant = 50
  2. Sagen Sie dem Inhalt, viewdass sein Layout verschmutzt ist und dass das Autolayout das Layout neu berechnen sollte:

    self.view.setNeedsLayout()
  3. Weisen Sie das Layout im Animationsblock an, das Layout neu zu berechnen. Dies entspricht dem direkten Einstellen der Frames (in diesem Fall werden die Frames durch das automatische Layout festgelegt):

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

Vollständigstes einfachstes 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()stellen Sie sicher, dass der Startpunkt für die Animation aus dem Status stammt, in dem alte Einschränkungen angewendet wurden (falls einige andere Einschränkungen geändert wurden, die nicht in die Animation aufgenommen werden 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

Da wir mit iOS 10 einen neuen Animationsmechanismus haben - UIViewPropertyAnimatorsollten wir wissen, dass im Grunde der gleiche 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 animatores sich um eine Kapselung der Animation handelt, können wir darauf verweisen und sie später aufrufen. Da wir im Animationsblock jedoch nur das Autolayout anweisen, die Frames neu zu berechnen, müssen wir die Einschränkungen vor dem Aufruf ä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 des Änderns von Einschränkungen und des Startens eines Animators ist wichtig. Wenn wir nur die Einschränkungen ändern und unseren Animator für einen späteren Zeitpunkt verlassen, kann der nächste Neuzeichnungszyklus eine Neuberechnung des automatischen Layouts aufrufen und die Änderung wird nicht animiert.

Denken Sie auch daran, dass ein einzelner Animator nicht wiederverwendbar ist. Sobald Sie ihn ausgeführt haben, können Sie ihn nicht mehr "erneut ausführen". Ich denke, es gibt keinen guten Grund, den Animator in der Nähe zu halten, es sei denn, wir verwenden ihn zur Steuerung einer interaktiven Animation.

Milan Nosáľ
quelle
👍 Funktioniert auch in Swift 5
hervorragend
layoutIfNeeded () ist der Schlüssel
Medhi
15

Storyboard, Code, Tipps und ein paar Fallstricke

Die anderen Antworten sind in Ordnung, aber diese zeigt einige ziemlich wichtige Fallstricke beim Animieren von Einschränkungen anhand eines aktuellen Beispiels. 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 erhalten. 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 einigen Experimenten stellte ich fest, dass man die Einschränkung aus der Ansicht OBEN (auch bekannt als Übersicht) der beiden Ansichten erhalten muss, in denen die Einschränkung definiert ist. 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 die Filtermethode von Swift, 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 Einschränkungen mit Swift

Nota Bene - Dieses Beispiel ist die Storyboard- / Codelösung und setzt voraus, dass im Storyboard Standardeinschränkungen festgelegt wurden. Man kann die Änderungen dann mit Code animieren.

Angenommen, Sie erstellen eine Eigenschaft zum Filtern nach genauen Kriterien und gelangen zu einem bestimmten Wendepunkt für Ihre Animation (natürlich können Sie auch nach einem Array filtern und eine Schleife durchführen, 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

        }
    }
}

Die vielen falschen Wendungen

Diese Notizen sind wirklich eine Reihe von Tipps, die ich für mich selbst geschrieben habe. Ich habe alle Dinge persönlich und schmerzhaft getan. Hoffentlich kann dieser Leitfaden andere schonen.

  1. 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-Datei eines Storyboards verloren ging und dazu führte, dass die animierte Ansicht (während der Arbeit) abgedeckt wurde.

  2. Nehmen Sie sich immer eine Minute Zeit, um die Dokumentation (neu und alt), die Schnellhilfe und die Überschriften zu lesen. Apple nimmt ständig Änderungen vor, um die AutoLayout-Einschränkungen besser zu verwalten (siehe Stapelansichten). Oder zumindest das AutoLayout-Kochbuch . Denken Sie daran, dass die besten Lösungen manchmal in den älteren Dokumentationen / Videos enthalten sind.

  3. Spielen Sie mit den Werten in der Animation und erwägen Sie die Verwendung anderer animateWithDuration-Varianten.

  4. Codieren Sie bestimmte Layoutwerte nicht als Kriterien zum Bestimmen von Änderungen an anderen Konstanten, sondern verwenden Sie Werte, mit denen Sie den Speicherort der Ansicht bestimmen können. CGRectContainsRectist ein Beispiel

  5. Zögern Sie nicht, bei Bedarf die Layoutränder zu verwenden, die einer Ansicht zugeordnet sind, 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 im Storyboard haben Einschränkungen, die an die Eigenschaft self.viewName.constraints angehängt sind
  7. Ändern Sie Ihre Prioritäten für Einschränkungen auf weniger als 1000. Ich habe meine im Storyboard auf 250 (niedrig) oder 750 (hoch) gesetzt. (Wenn Sie versuchen, eine 1000-Priorität in einen Code zu ändern, stürzt die App ab, da 1000 erforderlich ist.)
  8. Versuchen Sie nicht sofort, activConstraints und disableConstraints zu verwenden (sie haben ihren Platz, aber wenn Sie nur lernen oder wenn Sie ein Storyboard verwenden, bedeutet dies wahrscheinlich, dass Sie zu viel tun - sie haben jedoch einen Platz, wie unten gezeigt).
  9. Verwenden Sie addConstraints / removeConstraints nur, wenn Sie dem Code wirklich eine neue Einschränkung hinzufügen. Ich habe festgestellt, dass ich die Ansichten im Storyboard meistens mit den gewünschten Einschränkungen (Platzieren der Ansicht außerhalb des Bildschirms) gestalte und dann im Code die zuvor im Storyboard erstellten Einschränkungen animiere, um die Ansicht zu verschieben.
  10. Ich habe viel Zeit damit verschwendet, Einschränkungen mit der neuen NSAnchorLayout-Klasse und den Unterklassen aufzubauen. Diese funktionieren einwandfrei, aber es hat eine Weile gedauert, bis mir klar wurde, dass alle Einschränkungen, die ich brauchte, bereits im Storyboard vorhanden waren. Wenn Sie Einschränkungen in Code erstellen, verwenden Sie diese Methode mit Sicherheit, um Ihre Einschränkungen zu aggregieren:

Schnelle Auswahl an Lösungen, die bei der Verwendung von Storyboards vermieden werden sollten

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 oder die einfacheren Tipps vergessen, z. B. wo das layoutIfNeeded hinzugefügt werden soll, passiert höchstwahrscheinlich nichts: In diesem Fall haben Sie möglicherweise eine halbherzige Lösung wie diese:

NB - Nehmen Sie sich einen Moment Zeit, um den Abschnitt AutoLayout unten 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 vollständige Kontrolle über das Animieren von Änderungen benötigen, die von Auto Layout vorgenommen wurden, müssen Sie Ihre Einschränkungsänderungen programmgesteuert vornehmen. Das Grundkonzept ist für iOS und OS X gleich, es gibt jedoch einige geringfügige Unterschiede.

In einer iOS-App sieht Ihr Code folgendermaßen aus:

[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 Animationen mit Ebenenunterstützung verwenden, müssen Sie die Konstante mit dem Animator der Einschränkung animieren:

[[constraint animator] setConstant:42];

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

Pass gut auf

Oft gibt es in der Dokumentation kleine Notizen oder Codeteile, die zu größeren Ideen führen. Das Anhängen von Einschränkungen für das automatische Layout an dynamische Animatoren ist beispielsweise eine große Idee.

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

Tommie C.
quelle
11

Schnelle Lösung:

yourConstraint.constant = 50
UIView.animate(withDuration: 1.0, animations: {
    yourView.layoutIfNeeded
})
Nazariy Vlizlo
quelle
7

Arbeitslösung 100% schnell 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 hier funktionieren nicht. Sie sollten sie derzeit 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.
}
C0mrade
quelle
4

Ich habe versucht, Einschränkungen zu animieren, und es war nicht einfach, eine gute Erklärung zu finden.

Was andere Antworten sagen, ist völlig richtig: Sie müssen [self.view layoutIfNeeded];drinnen anrufen animateWithDuration: animations:. Der andere wichtige Punkt ist jedoch, Zeiger für jeden zu haben, den NSLayoutConstraintSie animieren möchten.

Ich habe ein Beispiel in GitHub erstellt .

Gabriel.Massana
quelle
4

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 ist, die auf eine benutzerdefinierte Ansicht (CalendarView) verweist. Ich habe .layoutIfNeeded () in self.view und NICHT in self.calendarView aufgerufen

Ich hoffe das hilft.

Edoardo Vicoli
quelle
3

Es gibt einen Artikel darüber: http://weblog.invasivecode.com/post/42362079291/auto-layout-and-core-animation-auto-layout-was

In dem er so codierte:

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

DD
quelle
1

Im Zusammenhang mit der Einschränkungsanimation möchte ich eine bestimmte Situation erwähnen, in der ich eine Einschränkung sofort in einer Benachrichtigung mit Tastaturöffnung animiert habe.

Die Einschränkung definierte einen oberen Bereich von einem Textfeld bis zum oberen Rand des Containers. Beim Öffnen der Tastatur teile ich die Konstante einfach durch 2.

Ich konnte keine konsistente glatte Einschränkungsanimation direkt in der Tastaturbenachrichtigung erzielen. Etwa die Hälfte der Zeit würde die Ansicht einfach an ihre neue Position springen - ohne zu animieren.

Mir ist aufgefallen, dass durch das Öffnen 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.

ivanferdelja
quelle