Пользовательская анимация открытия/закрытия UIViewController

Довольно часто можно встретить приложение, которое использует пользовательскую анимацию презентации UIViewController. Это могут быть экраны, которые наполовину перекрывают предыдущий, например, ApplePay, или же экраны, анимация появления и исчезновения которых отличается от системной. В этой статье будет представлено решение, которое презентует экран с анимацией увеличения и позволяет выполнить закрытие экрана интерактивным жестом.
Задача
Требуется, чтобы при презентации исходное изображение, на которое нажал пользователь увеличилось и растянулось по ширине экрана. При этом фон должен быть анимировано затемнен. Также необходимо разработать возможность закрытия экрана с помощью интерактивного жеста (оттягивания).
Анимация открытия или закрытия
Задается анимация для UIViewController с помощью свойства transitioningDelegate. Поэтому потребуется реализовать протокол UIViewControllerTransitioningDelegate.
Воспользуемся методами animationController(forPresented:presenting:source:) и animationController(forDismissed:). Данные методы возвращают некоторый объект, реализующий UIViewControllerAnimatedTransitioning.
Поскольку в задаче сказано о том, что выполняется анимация фона и изображения, но никак не frame, то блок анимации остается пустым. Для того, чтобы анимация была пружинистой, используются usingSpringWithDamping и initialSpringVelocity аргументы.
Анимация фона и изображения
Если обратиться к документации UIKit, то можно найти компонент, который позволяет добавить subview во время анимации открытия/закрытия экрана. Этот компонент — UIPresentationController.
Реализация максимально простая. В containerView добавляется фон — dimmingView и imageView, которые будут анимированы. Ключевые методы — presentationTransitionWillBegin() и dismissalTransitionWillBegin(). Именно здесь происходит подготовка и сама анимация с помощью вызова animate(alongsideTransition:completion:). Также не стоит забывать выполнить завершающие действия в методах presentationTransitionDidEnd(_:) и dismissalTransitionDidEnd(_: Bool). с учетом того, была ли завершена операция открытия или закрытия.
Обработка интерактивного жеста закрытия
Для решения этой задачи необходимо реализовать метод interactionControllerForDismissal(using:). Этот метод вовращает некоторый объект, реализующий UIViewControllerInteractiveTransitioning. В UIKit уже существует компонент, которым можно воспользоваться для этих целей — UIPercentDrivenInteractiveTransition.
Итак, чтобы запрограммировать закрытие экрана с помощью жеста нужно добавить обработчик жеста и обновить состояние UIPercentDrivenInteractiveTransition.
В итоге для презентации экрана будет достаточно метода present(_:animated:).
Заключение
Совсем скоро WWDC 2022. Каждый раз в Apple удивляются чем-то новым, в том числе и в отношении UI. Наверняка решение, предложенное в статье можно реализовать еще лучше, например воспользовавшись возможностями UIKitDynamics, но об этом в другой раз.