Hallo Habr! Jeder mag reaktionsschnelle Apps. Noch besser, wenn sie relevante Animationen haben. In diesem Artikel werde ich mit all dem "Fleisch" erzählen und zeigen, wie man alles richtig zeigt, versteckt, dreht, wirbelt und alles mit Popup-Bildschirmen macht.

Zunächst wollte ich einen Artikel schreiben, in dem es heißt, dass unter iOS 10 ein praktischer UIViewPropertyAnimator
erscheint, der das Problem unterbrochener Animationen löst. Jetzt können sie gestoppt, invertiert, fortgesetzt oder abgebrochen werden. Apple nennt diese Schnittstelle Fluid .
Aber dann wurde mir klar: Es ist schwer darüber zu sprechen, die Animation von Controllern zu unterbrechen, ohne zu beschreiben, wie diese Übergänge korrekt animiert werden. Daher wird es zwei Artikel geben. In diesem Abschnitt erfahren Sie, wie Sie den Bildschirm richtig ein- und ausblenden und wie Sie ihn im nächsten Schritt unterbrechen .
Wie funktionieren sie?
UIViewController
verfügt über eine transitioningDelegate
Eigenschaft. Dies ist ein Protokoll mit verschiedenen Funktionen, von denen jedes ein Objekt zurückgibt:
animationController
für Animation,interactionController
zum Unterbrechen von Animationen,presentationController
für die Anzeige: Hierarchie, Rahmen usw.

Basierend auf all dem erstellen wir ein Popup-Fenster:

Kochregler
Sie können den Übergang für modale Controller und für UINavigationController
(funktioniert über UINavigationControllerDelegate
).
Wir werden modale Übergänge betrachten. Das Controller-Setup vor der Show ist etwas ungewöhnlich:
class ParentViewController: UIViewController { private let transition = PanelTransition()
- Erstellen Sie ein Objekt, das den Übergang beschreibt.
transitioningDelegate
als weak
markiert, daher müssen Sie den transition
separat durch einen strong
Link speichern. - Wir setzen unseren Übergang auf
transitioningDelegate
. - Um die Anzeigemethode in
presentationController
zu steuern, müssen Sie .custom
für modalPresentationStyle.
angeben modalPresentationStyle.
.
Der gezeigte Controller weiß überhaupt nicht, wie er angezeigt wird. Und das ist gut.
Im halben Bildschirm anzeigen
Beginnen wir den Code für PanelTransition
mit presentationController
. Sie haben damit gearbeitet, wenn Sie Popups über den UIPopoverController
. PresentationController
steuert die Anzeige des Controllers: Frame, Hierarchie usw. Er entscheidet, wie Popovers auf dem iPad angezeigt werden sollen: Mit welchem Rahmen und auf welcher Seite der Schaltfläche wird der Hintergrund des Fensters unscharf und unter diesem dunkler.

Unsere Struktur ist ähnlich: Wir werden den Hintergrund abdunkeln, den Rahmen nicht im Vollbildmodus anzeigen:

presentationController(forPresented:, presenting:, source:)
in der Methode PresentationController
presentationController(forPresented:, presenting:, source:)
die PresentationController
Klasse zurück:
class PanelTransition: NSObject, UIViewControllerTransitioningDelegate { func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? { return presentationController = PresentationController(presentedViewController: presented, presenting: presenting ?? source) }
Warum werden 3 Controller übertragen und was ist die Quelle?Source
ist der Controller, auf dem wir die Animation der Show aufgerufen haben. Der Controller, der an der Tranche teilnimmt, ist jedoch der erste der Hierarchie mit definesPresentationContext = true
. Wenn sich die Steuerung ändert, befindet sich die tatsächliche Anzeigesteuerung im presenting.
Parameter presenting.
Jetzt können Sie die PresentationController
Klasse implementieren. Lassen Sie uns zunächst den zukünftigen Controller einrahmen. frameOfPresentedViewInContainerView
gibt es eine frameOfPresentedViewInContainerView
Methode. Lassen Sie den Controller die untere Hälfte des Bildschirms einnehmen:
class PresentationController: UIPresentationController { override var frameOfPresentedViewInContainerView: CGRect { let bounds = containerView!.bounds let halfHeight = bounds.height / 2 return CGRect(x: 0, y: halfHeight, width: bounds.width, height: halfHeight) } }
Sie können das Projekt starten und versuchen, den Bildschirm anzuzeigen, aber es wird nichts passieren. Dies liegt daran, dass wir die Ansichtshierarchie jetzt selbst verwalten und die Controller-Ansicht manuell hinzufügen müssen:
Es muss noch ein Frame für presentedView
. containerViewDidLayoutSubviews
ist der beste Ort, da wir auf diese Weise auf Bildschirmrotation reagieren können:
Jetzt kannst du rennen. Die Animation ist Standard für UIModalTransitionStyle.coverVertical
, aber der Rahmen ist halb so groß.
Verdunkeln Sie den Hintergrund
Die nächste Aufgabe besteht darin, den Hintergrundcontroller abzudunkeln, um sich auf das zu konzentrieren, was angezeigt wird.
Wir werden von PresentationController
erben und es durch eine neue Klasse in der PanelTransition
Datei PanelTransition
. In der neuen Klasse gibt es nur einen Code zum Dimmen.
class DimmPresentationController: PresentationController
Erstellen Sie eine Ansicht, die überlagert wird:
private lazy var dimmView: UIView = { let view = UIView() view.backgroundColor = UIColor(white: 0, alpha: 0.3) view.alpha = 0 return view }()
Wir werden die alpha
Ansichten entsprechend der Übergangsanimation ändern. Es gibt 4 Methoden:
presentationTransitionWillBegin
presentationTransitionDidEnd
dismissalTransitionWillBegin
dismissalTransitionDidEnd
Der erste ist der schwierigste. Fügen dimmView
der Hierarchie dimmView
, legen Sie den Rahmen ab und starten Sie die Animation:
override func presentationTransitionWillBegin() { super.presentationTransitionWillBegin() containerView?.insertSubview(dimmView, at: 0) performAlongsideTransitionIfPossible { [unowned self] in self.dimmView.alpha = 1 } }
Die Animation wird mit einer Hilfsfunktion gestartet:
private func performAlongsideTransitionIfPossible(_ block: @escaping () -> Void) { guard let coordinator = self.presentedViewController.transitionCoordinator else { block() return } coordinator.animate(alongsideTransition: { (_) in block() }, completion: nil) }
Wir setzen den Frame für dimmView
in containerViewDidLayoutSubviews
(wie beim letzten Mal):
override func containerViewDidLayoutSubviews() { super.containerViewDidLayoutSubviews() dimmView.frame = containerView!.frame }
Die Animation kann unterbrochen und abgebrochen werden. Wenn sie abgebrochen wird, muss dimmView
aus der Hierarchie entfernt werden:
override func presentationTransitionDidEnd(_ completed: Bool) { super.presentationTransitionDidEnd(completed) if !completed { self.dimmView.removeFromSuperview() } }
Der umgekehrte Vorgang beginnt in den Hide-Methoden. Jetzt müssen Sie dimmView
nur entfernen, wenn die Animation abgeschlossen ist.
override func dismissalTransitionWillBegin() { super.dismissalTransitionWillBegin() performAlongsideTransitionIfPossible { [unowned self] in self.dimmView.alpha = 0 } } override func dismissalTransitionDidEnd(_ completed: Bool) { super.dismissalTransitionDidEnd(completed) if completed { self.dimmView.removeFromSuperview() } }
Jetzt wird der Hintergrund dunkler.
Wir steuern die Animation
Wir zeigen den Controller von unten
Jetzt können wir das Erscheinungsbild des Controllers animieren. PanelTransition
in der PanelTransition
Klasse die Klasse zurück, die die Darstellungsanimation steuert:
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { return PresentAnimation() }
Die Implementierung des Protokolls ist einfach:
extension PresentAnimation: UIViewControllerAnimatedTransitioning { func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return duration } func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { let animator = self.animator(using: transitionContext) animator.startAnimation() } func interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating { return self.animator(using: transitionContext) } }
Der Schlüsselcode ist etwas komplizierter:
class PresentAnimation: NSObject { let duration: TimeInterval = 0.3 private func animator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating {
UIViewPropertyAnimator funktioniert unter iOS 9 nichtDie Problemumgehung ist recht einfach: Sie müssen nicht den Animator im animateTransition
Code verwenden, sondern die alte UIView.animate…
API UIView.animate…
Beispiel:
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { let to = transitionContext.view(forKey: .to)! let finalFrame = transitionContext.finalFrame(for: transitionContext.viewController(forKey: .to)!) to.frame = finalFrame.offsetBy(dx: 0, dy: finalFrame.height) UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: [.curveEaseOut], animations: { to.frame = finalFrame }) { (_) in transitionContext.completeTransition(!transitionContext.transitionWasCancelled) } } , `interruptibleAnimator(using transitionContext:)`
Wenn Sie keinen Interruptible erstellen, kann die InterruptibleAnimator-Methode weggelassen werden. Diskontinuität wird im nächsten Artikel berücksichtigt, abonnieren.
Verstecken Sie den Controller
Alles ist gleich, nur in die entgegengesetzte Richtung. Erstellen DismissAnimation
in Panel Transition
die DismissAnimation
Klasse:
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { return DismissAnimation() }
Und wir erkennen es. DismissAnimation
Class:
class DismissAnimation: NSObject { let duration: TimeInterval = 0.3 private func animator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating { let from = transitionContext.view(forKey: .from)! let initialFrame = transitionContext.initialFrame(for: transitionContext.viewController(forKey: .from)!) let animator = UIViewPropertyAnimator(duration: duration, curve: .easeOut) { from.frame = initialFrame.offsetBy(dx: 0, dy: initialFrame.height) } animator.addCompletion { (position) in transitionContext.completeTransition(!transitionContext.transitionWasCancelled) } return animator } } extension DismissAnimation: UIViewControllerAnimatedTransitioning { func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return duration } func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { let animator = self.animator(using: transitionContext) animator.startAnimation() } func interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating { return self.animator(using: transitionContext) } }
An diesem Ort können Sie mit den Parteien experimentieren:
- Ein alternatives Szenario kann unten erscheinen.
- rechts - schnelle Menüführung;
- oben - Informationsnachricht:

Dodo Pizza , Snack und Savey
Das nächste Mal fügen wir ein interaktives Schließen mit einer Geste hinzu und unterbrechen dann die Animation. Wenn Sie nicht warten können, befindet sich das vollständige Projekt bereits auf dem Github.
Und hier ist der zweite Teil des Artikels angekommen.
Abonnieren Sie den Dodo Pizza Mobile-Kanal.