Хабрахабр

[Из песочницы] История одного вью-контроллера, который хотел показываться красиво

И не было у него ни картинки, ни текста, ни даже малюсенькой бизнес логики. Жил был скромный вью-контроллер VCYellow. Жил он обычной вью-контроллерской жизнью.

Его товарищ вью-контроллер VCMain иногда презентовал его миру:

class VCMain: UIViewController {
...
@IBAction func onBtnTapMeTapped(_ sender: Any) { let vcYellow = self.storyboard!.instantiateViewController(withIdentifier: "VCYellow") as! VCYellow self.present(vcYellow, animated: true, completion: nil)
}

А VCYellow в свою очередь скрывался при помощи единственной кнопки "X", которой он, кстати говоря, очень гордился:

class VCYellow: UIViewController {
...
@IBAction func onBtnCloseTapped(_ sender: Any) { self.dismiss(animated: true, completion: nil)
}

И выглядело это не то чтобы плохо, но скучно и обыденно:

Да так, чтобы можно было эту красоту менять потом по праздникам или просто в честь хорошего настроения. Но была у нашего героя мечта научиться показываться и скрываться по-красоте.

Шли года… и так и осталась бы мечта мечтой, если бы не узнал VCYellow о магии под названием:

UIViewControllerTransitioningDelegate

Как раз то, о чём мечтал наш контроллер.
Прочитал он в древних свитках как использовать заклятие и начал готовиться.
Записал себе шпаргалку с самим заклинанием, чтобы не забыть: А сила этой магии в том, что даёт она возможность подсунуть соответствующий аниматор для показа и для скрытия вью-контроллера.

extension VCYellow: UIViewControllerTransitioningDelegate func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { return AnimatorDismiss(endFrame: self.startFrame) }
}

В ней он тщательно расписал, что для показа нужно использовать аниматор AnimatorPresent, а при закрытии AnimatorDismiss.
Ну и в качестве помощи обоим аниматорам было решено передать фрейм главной кнопки из VCMain

Потому как без правильного настроя, как известно, никакая магия не работает: А потом и сам морально настроился.

override func viewDidLoad() { super.viewDidLoad() self.modalPresentationStyle = .custom self.transitioningDelegate = self
}

Попросил он своего друга VCMain презентануть себя, чтобы проверить как магия сработает и… сработала она никак…
Оказалось, что AnimatorPresent и AnimatorDismiss сами собой не появляются.

Поковырялся в нужном разделе древних свитков и узнал, что для создания аниматора достаточно двух вещей. Останавливаться было уже поздно и наш герой решил создать необходимые аниматоры.

Во-первых надо задать время, отведённое для анимации:

func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return 0.3
}

а во-вторых обозначить саму анимацию:


func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { //1 guard let vcTo = transitionContext.viewController(forKey: .to), let snapshot = vcTo.view.snapshotView(afterScreenUpdates: true) else { return } //2 let vContainer = transitionContext.containerView //3 vcTo.view.isHidden = true vContainer.addSubview(vcTo.view) //4 snapshot.frame = self.startFrame vContainer.addSubview(snapshot) UIView.animate(withDuration: 0.3, animations: { //5 snapshot.frame = (transitionContext.finalFrame(for: vcTo)) }, completion: { success in //6 vcTo.view.isHidden = false snapshot.removeFromSuperview() transitionContext.completeTransition(true) })
}

  1. Вытащить презентуемый вью-контроллер(в нашем случае VCYellow) и сфоткать его. Фотка нужна для упрощения анимации.
  2. Получить вьюшку, на которой будет происходить анимационное колдунство. Назовем её контекст.
  3. Нацепить вьюху конечного контроллера на контекст и скрыть её. Показать
  4. её было решено после того как закончится анимация.
  5. Подготовить фотку для анимации. Уменьшить до начальных размеров и кинуть на контекст.
  6. Расщеперить фотку на весь экран, тем самым анимировав процесс презентации.
  7. После окончания анимации показать настоящую вьюху конечного контроллера,
  8. избавиться от фотки и сообщить, что действо окончено.

В результате вышел вот такой аниматор для показа:

import UIKit class AnimatorPresent: NSObject, UIViewControllerAnimatedTransitioning { let startFrame: CGRect init(startFrame: CGRect) { self.startFrame = startFrame } func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return 0.3 } func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { guard let vcTo = transitionContext.viewController(forKey: .to), let snapshot = vcTo.view.snapshotView(afterScreenUpdates: true) else { return } let vContainer = transitionContext.containerView vcTo.view.isHidden = true vContainer.addSubview(vcTo.view) snapshot.frame = self.startFrame vContainer.addSubview(snapshot) UIView.animate(withDuration: 0.3, animations: { snapshot.frame = (transitionContext.finalFrame(for: vcTo)) }, completion: { success in vcTo.view.isHidden = false snapshot.removeFromSuperview() transitionContext.completeTransition(true) }) }
}

А после этого несложно было написать аниматор для скрывания, который делает примерно то же самое, но наоборот:

import UIKit class AnimatorDismiss: NSObject, UIViewControllerAnimatedTransitioning { let endFrame: CGRect init(endFrame: CGRect) { self.endFrame = endFrame } func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return 0.3 } func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { guard let vcTo = transitionContext.viewController(forKey: .to), let vcFrom = transitionContext.viewController(forKey: .from), let snapshot = vcFrom.view.snapshotView(afterScreenUpdates: true) else { return } let vContainer = transitionContext.containerView vContainer.addSubview(vcTo.view) vContainer.addSubview(snapshot) vcFrom.view.isHidden = true UIView.animate(withDuration: 0.3, animations: { snapshot.frame = self.endFrame }, completion: { success in transitionContext.completeTransition(true) }) }
}

Закончив все доделки, VCYellow опять попросил своего друга VCMain презентовать себя и о чудо!

Мечта VCYellow сбылась! Магия сработала! Теперь он может показываться и скрываться как ему захочется и ничто не будет ограничивать его фантазию!

Проект-пример можно скачать тут

Статья, которую я использовал для вдохновения находится тут

Показать больше

Похожие публикации

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»