Pop up! Transkribiert auf iOS

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() // 1 @IBAction func openDidPress(_ sender: Any) { let child = ChildViewController() child.transitioningDelegate = transition // 2 child.modalPresentationStyle = .custom // 3 present(child, animated: true) } } 

  1. Erstellen Sie ein Objekt, das den Übergang beschreibt. transitioningDelegate als weak markiert, daher müssen Sie den transition separat durch einen strong Link speichern.
  2. Wir setzen unseren Übergang auf transitioningDelegate .
  3. 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:


 // PresentationController.swift override func presentationTransitionWillBegin() { super.presentationTransitionWillBegin() containerView?.addSubview(presentedView!) } 

Es muss noch ein Frame für presentedView . containerViewDidLayoutSubviews ist der beste Ort, da wir auf diese Weise auf Bildschirmrotation reagieren können:


 // PresentationController.swift override func containerViewDidLayoutSubviews() { super.containerViewDidLayoutSubviews() presentedView?.frame = frameOfPresentedViewInContainerView } 

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 { // transitionContext.view    ,   let to = transitionContext.view(forKey: .to)! let finalFrame = transitionContext.finalFrame(for: transitionContext.viewController(forKey: .to)!) //   ,     PresentationController //      to.frame = finalFrame.offsetBy(dx: 0, dy: finalFrame.height) let animator = UIViewPropertyAnimator(duration: duration, curve: .easeOut) { to.frame = finalFrame //   ,     } animator.addCompletion { (position) in //  ,      transitionContext.completeTransition(!transitionContext.transitionWasCancelled) } return animator } } 

UIViewPropertyAnimator funktioniert unter iOS 9 nicht

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

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


All Articles