Verwenden von UIViewPropertyAnimator zum Erstellen benutzerdefinierter Animationen

Das Erstellen von Animationen ist großartig. Sie sind ein wichtiger Bestandteil der Richtlinien für die Benutzeroberfläche von iOS . Animationen helfen dabei, die Aufmerksamkeit des Benutzers auf wichtige Dinge zu lenken oder die Anwendung einfach weniger langweilig zu machen.

Es gibt verschiedene Möglichkeiten, Animationen in iOS zu implementieren. Der wahrscheinlich beliebteste Weg ist die Verwendung von UIView.animate (withDuration: animations :) . Sie können eine Bildebene mit CABasicAnimation animieren. Darüber hinaus können Sie mit UIKit benutzerdefinierte Animationen konfigurieren, um den Controller mithilfe von UIViewControllerTransitioningDelegate anzuzeigen .

In diesem Artikel möchte ich einen weiteren aufregenden Weg zum Animieren von Ansichten diskutieren - UIViewPropertyAnimator . Diese Klasse bietet viel mehr Verwaltungsfunktionen als ihr Vorgänger UIView.animat e. Verwenden Sie diese Option, um temporäre, interaktive und unterbrochene Animationen zu erstellen. Außerdem ist es möglich, den Animator schnell zu ändern.

Einführung in UIViewPropertyAnimator


UIViewPropertyAnimator wurde in iOS 10 eingeführt . Sie können damit objektorientiert Animationen erstellen. Schauen wir uns ein Beispiel für eine Animation an, die mit dem UIViewPropertyAnimator erstellt wurde .

Bild

So war es bei der Verwendung von UIView.

UIView.animate(withDuration: 0.3) { view.frame = view.frame.offsetBy(dx: 100, dy: 0) } 

Und so geht's mit dem UIViewPropertyAnimator :

 let animator = UIViewPropertyAnimator(duration:0.3, curve: .linear) { view.frame = view.frame.offsetBy(dx:100, dy:0) } animator.startAnimation() 

Wenn Sie die Animation überprüfen müssen, erstellen Sie einfach einen Spielplatz und führen Sie den Code wie unten gezeigt aus. Beide Codefragmente führen zum gleichen Ergebnis.

Bild

Sie könnten denken, dass es in diesem Beispiel keinen großen Unterschied gibt. Was bringt es also, eine neue Methode zum Erstellen von Animationen hinzuzufügen? UIViewPropertyAnimator wird nützlicher, wenn Sie interaktive Animationen erstellen müssen.

Interaktive und unterbrochene Animation


Erinnern Sie sich an die klassische Geste „Fingerwischen zum Entsperren des Geräts“? Oder die Geste "Bewegen Sie Ihren Finger auf dem Bildschirm von unten nach oben", um das Control Center zu öffnen? Dies sind großartige Beispiele für interaktive Animationen. Sie können das Bild mit dem Finger bewegen und dann loslassen. Das Bild kehrt dann in seine ursprüngliche Position zurück. Außerdem können Sie das Bild während der Animation erfassen und mit dem Finger weiter bewegen.

UIView-Animationen bieten keine einfache Möglichkeit, den Prozentsatz der Fertigstellung der Animation zu steuern. Sie können die Animation nicht in der Mitte einer Schleife anhalten und die Ausführung nach einer Unterbrechung fortsetzen.

In diesem Fall werden wir über UIViewPropertyAnimator sprechen. Als Nächstes sehen wir uns an, wie Sie in wenigen Schritten auf einfache Weise vollständig interaktive, unterbrochene Animationen und umgekehrte Animationen erstellen können.

Vorbereitung des Startprojekts


Zuerst müssen Sie das Starterprojekt herunterladen . Sobald Sie das Archiv geöffnet haben, finden Sie die CityGuide- Anwendung, mit der Benutzer ihre Ferien planen können. Der Benutzer kann durch die Liste der Städte scrollen und dann eine detaillierte Beschreibung mit detaillierten Informationen über die Stadt öffnen, die ihm gefällt.

Berücksichtigen Sie den Quellcode des Projekts, bevor Sie mit der Erstellung schöner Animationen beginnen. Folgendes können Sie in einem Projekt finden, indem Sie es in Xcode öffnen:

  1. ViewController.swift : Der Hauptanwendungscontroller mit einer UICollectionView , die ein Array von Stadtobjekten anzeigt.
  2. CityCollectionViewCell.swift: Zelle zum Anzeigen von City . Tatsächlich gelten in diesem Artikel die meisten Änderungen für diese Klasse. Möglicherweise stellen Sie fest, dass descriptionLabel und closeButton bereits in der Klasse definiert sind. Nach dem Starten der Anwendung werden diese Objekte jedoch ausgeblendet. Keine Sorge, sie werden etwas später sichtbar sein. Diese Klasse verfügt auch über die Eigenschaften collectionView und index . Später werden sie für die Animation verwendet.
  3. CityCollectionViewFlowLayout.swift: Diese Klasse ist für das horizontale Scrollen verantwortlich. Wir werden es noch nicht ändern.
  4. City.swift : Das Hauptanwendungsmodell verfügt über eine Methode, die in ViewController verwendet wurde.
  5. Main.storyboard: Dort finden Sie die Benutzeroberfläche für ViewController und CityCollectionViewCell .

Versuchen wir, die Beispielanwendung zu erstellen und auszuführen. Als Ergebnis erhalten wir Folgendes.

cityguideapp-iphone8

Implementierung der Bereitstellungs- und Reduzierungsanimation


Nach dem Start der Anwendung wird eine Liste der Städte angezeigt. Der Benutzer kann jedoch nicht mit Objekten in Form von Zellen interagieren. Jetzt müssen Sie Informationen für jede Stadt anzeigen, wenn der Benutzer auf eine der Zellen klickt. Schauen Sie sich die endgültige Version der Anwendung an. Folgendes musste tatsächlich entwickelt werden:

Bild

Die Animation sieht gut aus, nicht wahr? Aber hier gibt es nichts Besonderes, es ist nur die Grundlogik des UIViewPropertyAnimator . Mal sehen, wie diese Art von Animation implementiert wird. Erstellen Sie eine collectionView- Methode (_: didSelectItemAt) und fügen Sie das folgende Codefragment am Ende der ViewController- Datei hinzu:

 func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { let selectedCell = collectionView.cellForItem(at: indexPath)! as! CityCollectionViewCell selectedCell.toggle() } 

Jetzt müssen wir die Umschaltmethode implementieren. Wechseln wir zu CityCollectionViewCell.swift und implementieren Sie diese Methode.

Fügen Sie zunächst die Statusaufzählung am Anfang der Datei direkt vor der Deklaration der CityCollectionViewCell- Klasse hinzu. Mit dieser Auflistung können Sie den Status einer Zelle verfolgen:

 private enum State { case expanded case collapsed var change: State { switch self { case .expanded: return .collapsed case .collapsed: return .expanded } } } 

Fügen Sie einige Eigenschaften hinzu, um die Animation in der CityCollectionViewCell- Klasse zu steuern:

 private var initialFrame: CGRect? private var state: State = .collapsed private lazy var animator: UIViewPropertyAnimator = { return UIViewPropertyAnimator(duration: 0.3, curve: .easeInOut) }() 

Die Variable initialFrame wird verwendet, um den Zellenrahmen zu speichern, bis die Animation ausgeführt wird. Der Status wird verwendet, um zu verfolgen, ob die Zelle erweitert oder reduziert ist. Die Animatorvariable wird zur Steuerung der Animation verwendet.

Fügen Sie nun die Umschaltmethode hinzu und rufen Sie sie von der close- Methode aus auf, zum Beispiel:

 @IBAction func close(_ sender: Any) { toggle() } func toggle() { switch state { case .expanded: collapse() case .collapsed: expand() } } 

Dann fügen wir zwei weitere Methoden hinzu: expand () undapse () . Wir werden ihre Umsetzung fortsetzen. Zunächst beginnen wir mit der Methode expansiond () :

 private func expand() { guard let collectionView = self.collectionView, let index = self.index else { return } animator.addAnimations { self.initialFrame = self.frame self.descriptionLabel.alpha = 1 self.closeButton.alpha = 1 self.layer.cornerRadius = 0 self.frame = CGRect(x: collectionView.contentOffset.x, y:0, width: collectionView.frame.width, height: collectionView.frame.height) if let leftCell = collectionView.cellForItem(at: IndexPath(row: index - 1, section: 0)) { leftCell.center.x -= 50 } if let rightCell = collectionView.cellForItem(at: IndexPath(row: index + 1, section: 0)) { rightCell.center.x += 50 } self.layoutIfNeeded() } animator.addCompletion { position in switch position { case .end: self.state = self.state.change collectionView.isScrollEnabled = false collectionView.allowsSelection = false default: () } } animator.startAnimation() } 

Wie viel Code. Lassen Sie mich Schritt für Schritt erklären, was passiert:

  1. Überprüfen Sie zunächst, ob collectionView und index ungleich Null sind. Andernfalls können wir die Animation nicht starten.
  2. Beginnen Sie als Nächstes mit der Erstellung der Animation, indem Sie animator.addAnimations aufrufen.
  3. Speichern Sie als Nächstes den aktuellen Frame, mit dem er in der Faltungsanimation wiederhergestellt wird.
  4. Dann setzen wir den Alpha-Wert für descriptionLabel und closeButton , um sie sichtbar zu machen.
  5. Entfernen Sie als Nächstes die abgerundete Ecke und legen Sie einen neuen Rahmen für die Zelle fest. Die Zelle wird im Vollbildmodus angezeigt.
  6. Als nächstes bewegen wir die Nachbarzellen.
  7. Rufen Sie nun die Methode animator.addComplete () auf, um die Interaktion des Sammlungsbildes zu deaktivieren. Dies verhindert, dass Benutzer während der Zellenerweiterung einen Bildlauf durchführen. Ändern Sie auch den aktuellen Status der Zelle. Es ist wichtig, den Status der Zelle zu ändern. Erst danach endet die Animation.

Fügen Sie nun eine Faltungsanimation hinzu. Kurz gesagt, es ist nur so, dass wir die Zelle in ihren vorherigen Zustand zurückversetzen:

 private func collapse() { guard let collectionView = self.collectionView, let index = self.index else { return } animator.addAnimations { self.descriptionLabel.alpha = 0 self.closeButton.alpha = 0 self.layer.cornerRadius = self.cornerRadius self.frame = self.initialFrame! if let leftCell = collectionView.cellForItem(at: IndexPath(row: index - 1, section: 0)) { leftCell.center.x += 50 } if let rightCell = collectionView.cellForItem(at: IndexPath(row: index + 1, section: 0)) { rightCell.center.x -= 50 } self.layoutIfNeeded() } animator.addCompletion { position in switch position { case .end: self.state = self.state.change collectionView.isScrollEnabled = true collectionView.allowsSelection = true default: () } } animator.startAnimation() } 

Jetzt ist es Zeit, die Anwendung zu kompilieren und auszuführen. Wenn Sie auf die Zelle klicken, wird die Animation angezeigt. Um das Bild zu schließen, klicken Sie auf das Kreuzsymbol in der oberen rechten Ecke.

Hinzufügen einer Gestenverarbeitung


Sie können behaupten, mit UIView.animate dasselbe Ergebnis zu erzielen . Was bringt es , einen UIViewPropertyAnimator zu verwenden ?

Nun, es ist Zeit, die Animation interaktiv zu gestalten. Fügen Sie einen UIPanGestureRecognizer und eine neue Eigenschaft namens popupOffset hinzu, um zu verfolgen, wie viel die Zelle verschoben werden kann. Deklarieren wir diese Variablen in der CityCollectionViewCell- Klasse:

 private let popupOffset: CGFloat = (UIScreen.main.bounds.height - cellSize.height)/2.0 private lazy var panRecognizer: UIPanGestureRecognizer = { let recognizer = UIPanGestureRecognizer() recognizer.addTarget(self, action: #selector(popupViewPanned(recognizer:))) return recognizer }() 

Fügen Sie dann die folgende Methode hinzu, um die Swipe-Definition zu registrieren:

 override func awakeFromNib() { self.addGestureRecognizer(panRecognizer) } 

Jetzt müssen Sie die popupViewPanned- Methode hinzufügen, um die Wischgeste zu verfolgen. Fügen Sie den folgenden Code in die CityCollectionViewCell ein :

 @objc func popupViewPanned(recognizer: UIPanGestureRecognizer) { switch recognizer.state { case .began: toggle() animator.pauseAnimation() case .changed: let translation = recognizer.translation(in: collectionView) var fraction = -translation.y / popupOffset if state == .expanded { fraction *= -1 } animator.fractionComplete = fraction case .ended: animator.continueAnimation(withTimingParameters: nil, durationFactor: 0) default: () } } 

Es gibt drei Zustände. Zu Beginn der Geste initialisieren wir den Animator mit der Umschaltmethode und halten ihn sofort an. Während der Benutzer die Zelle zieht, aktualisieren wir die Animation, indem wir die Eigenschaften des FractionComplete- Multiplikators festlegen. Dies ist die Hauptmagie des Animators, mit der er steuern kann. Wenn der Benutzer seinen Finger loslässt, wird die animator-Methode continueAnimation aufgerufen, um die Animation fortzusetzen. Dann bewegt sich die Zelle zur Zielposition.

Nach dem Starten der Anwendung können Sie die Zelle nach oben ziehen, um sie zu erweitern. Ziehen Sie dann die erweiterte Zelle nach unten, um sie zu reduzieren.

Die Animation sieht jetzt ziemlich gut aus, aber eine Unterbrechung der Animation in der Mitte ist nicht möglich. Um die Animation vollständig interaktiv zu gestalten, müssen Sie daher eine weitere Funktion hinzufügen - die Unterbrechung. Der Benutzer kann die Erweiterungs- / Reduzierungsanimation wie gewohnt starten. Die Animation sollte jedoch sofort angehalten werden, nachdem der Benutzer während des Animationszyklus auf die Zelle geklickt hat.

Dazu müssen Sie den Fortschritt der Animation speichern und diesen Wert berücksichtigen, um den Prozentsatz der Fertigstellung der Animation zu berechnen.

Deklarieren Sie zunächst eine neue Eigenschaft in CityCollectionViewCell :

 private var animationProgress: CGFloat = 0 

Aktualisieren Sie dann den .began- Block der popupViewPanned- Methode mit der folgenden Codezeile, um sich den Fortschritt zu merken:

 animationProgress = animator.fractionComplete 

Im Block .changed müssen Sie die folgende Codezeile aktualisieren, um den Fertigstellungsgrad korrekt zu berechnen:

 animator.fractionComplete = fraction + animationProgress 

Jetzt ist die Anwendung zum Testen bereit. Führen Sie das Projekt aus und sehen Sie, was passiert. Wenn alle Aktionen gemäß meinen Anweisungen korrekt ausgeführt werden, sollte die Animation folgendermaßen aussehen:

Bild

Animation umgekehrt


Sie können einen Fehler für die aktuelle Implementierung finden. Wenn Sie die Zelle ein wenig ziehen und dann an ihre ursprüngliche Position zurückbringen, wird die Zelle weiter erweitert, wenn Sie Ihren Finger loslassen. Beheben wir dieses Problem, um die interaktive Animation noch besser zu machen.
Aktualisieren wir den .end- Block der popupViewPanned- Methode wie unten beschrieben:

 let velocity = recognizer.velocity(in: self) let shouldComplete = velocity.y > 0 if velocity.y == 0 { animator.continueAnimation(withTimingParameters: nil, durationFactor: 0) break } switch state { case .expanded: if !shouldComplete && !animator.isReversed { animator.isReversed = !animator.isReversed } if shouldComplete && animator.isReversed { animator.isReversed = !animator.isReversed } case .collapsed: if shouldComplete && !animator.isReversed { animator.isReversed = !animator.isReversed } if !shouldComplete && animator.isReversed { animator.isReversed = !animator.isReversed } } animator.continueAnimation(withTimingParameters: nil, durationFactor: 0) 

Jetzt berücksichtigen wir die Geschwindigkeit der Geste, um zu bestimmen, ob die Animation umgekehrt werden soll.

Fügen Sie abschließend eine weitere Codezeile in den .changed- Block ein. Setzen Sie diesen Code rechts neben die Berechnung von animator.fractionComplete .

 if animator.isReversed { fraction *= -1 } 

Lassen Sie uns die Anwendung erneut ausführen. Jetzt sollte alles ohne Fehler funktionieren.

Bild

Pan-Geste korrigieren


Daher haben wir die Implementierung der Animation mit dem UIViewPropertyAnimator abgeschlossen . Es gibt jedoch einen unangenehmen Fehler. Möglicherweise haben Sie sie beim Testen der Anwendung getroffen. Das Problem ist, dass ein horizontales Scrollen der Zelle nicht möglich ist. Versuchen wir, nach links / rechts durch die Zellen zu streichen, und wir werden mit dem Problem konfrontiert.

Der Hauptgrund hängt mit dem von uns erstellten UIPanGestureRecognizer zusammen . Es fängt auch eine Wischgeste ab und steht in Konflikt mit der integrierten Gestenerkennung UICollectionView .

Obwohl der Benutzer immer noch durch den oberen / unteren Rand von Zellen oder den Abstand zwischen den Zellen scrollen kann, um durch Städte zu scrollen, mag ich eine so schlechte Benutzeroberfläche immer noch nicht. Lass es uns reparieren.

Um Konflikte zu lösen, müssen wir eine Delegatmethode namens gestRecognizerShouldBegin (_ :) implementieren. Diese Methode steuert, ob der Gestenerkenner weiterhin Berührungen interpretieren soll. Wenn Sie in der Methode false zurückgeben , ignoriert der Gestenerkenner Berührungen. Wir geben unserem eigenen Panorama-Erkennungswerkzeug die Möglichkeit, horizontale Bewegungen zu ignorieren.

Legen Sie dazu den Delegaten unseres Pan-Erkenners fest. Fügen Sie die folgende Codezeile in die panRecognizer- Initialisierung ein (Sie können den Code direkt vor dem Rückkehrerkenner platzieren :

 recognizer.delegate = self 

Anschließend implementieren wir die gestRecognizerShouldBegin (_ :) -Methode wie folgt:

 override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { return abs((panRecognizer.velocity(in: panRecognizer.view)).y) > abs((panRecognizer.velocity(in: panRecognizer.view)).x) } 

Wir öffnen / schließen, wenn die vertikale Geschwindigkeit größer als die horizontale Geschwindigkeit ist.

Wow! Lassen Sie uns die Anwendung erneut testen. Jetzt können Sie sich in der Liste der Städte bewegen, indem Sie nach links / rechts über die Zellen streichen.

Bild

Bonus: Benutzerdefinierte Synchronisierungsfunktionen


Bevor wir dieses Tutorial beenden, sprechen wir über Timing-Funktionen. Erinnern Sie sich noch an den Fall, als der Entwickler Sie aufforderte, eine benutzerdefinierte Synchronisierungsfunktion für die von Ihnen erstellte Animation zu implementieren?

Normalerweise sollten Sie die UIView.animation in CABasicAnimation ändern oder in CATransaction einschließen . Mit dem UIViewPropertyAnimator können Sie problemlos benutzerdefinierte Timing-Funktionen implementieren.

Timing- Funktionen (oder Beschleunigungsfunktionen) werden als Animationsgeschwindigkeitsfunktionen verstanden, die die Änderungsrate der einen oder anderen animierten Eigenschaft beeinflussen. Derzeit werden vier Typen unterstützt: EasyInOut, EasyIn, EasyOut, linear.

Ersetzen Sie die Animator-Initialisierung wie folgt durch diese Timing-Funktionen (versuchen Sie, Ihre eigene kubische Bezier-Kurve zu zeichnen):

 private lazy var animator: UIViewPropertyAnimator = { let cubicTiming = UICubicTimingParameters(controlPoint1: CGPoint(x: 0.17, y: 0.67), controlPoint2: CGPoint(x: 0.76, y: 1.0)) return UIViewPropertyAnimator(duration: 0.3, timingParameters: cubicTiming) }() 

Anstatt kubische Synchronisationsparameter zu verwenden, können Sie alternativ auch die Federsynchronisation verwenden, zum Beispiel:

  let springTiming = UISpringTimingParameters(mass: 1.0, stiffness: 2.0, damping: 0.2, initialVelocity: .zero) 

Versuchen Sie, das Projekt erneut zu starten und zu sehen, was passiert.

Fazit


Mit UIViewPropertyAnimator können Sie statische Bildschirme und Benutzerinteraktionen durch interaktive Animationen verbessern.

Ich weiß, dass Sie es kaum erwarten können, zu realisieren, was Sie in Ihrem eigenen Projekt gelernt haben. Wenn Sie diesen Ansatz in Ihrem Projekt anwenden, wird es sehr cool sein. Lassen Sie mich dies wissen, indem Sie unten einen Kommentar hinterlassen.

Als Referenz können Sie hier den endgültigen Entwurf herunterladen .

Weitere Links


Professionelle Animationen mit UIKit - https://developer.apple.com/videos/play/wwdc2017/230/

UIViewPropertyAnimator-Dokumentation für Apple-Entwickler - https://developer.apple.com/documentation/uikit/uiviewpropertyanimator

Source: https://habr.com/ru/post/de458970/


All Articles