Ответ
Задача: Реализация интерактивного, прерываемого жестом dismiss-перехода между экранами с одновременной нелинейной анимацией нескольких UI-элементов (фона, картинки, текста).
Исходная (неудачная) попытка:
Использование вложенных UIView.animate(withDuration:...) с разными параметрами. Это приводило к:
- Рассогласованию анимаций: Элементы двигались с разной скоростью и останавливались в разное время.
- Сложности с интерактивностью: Жест пользователя не мог плавно управлять прогрессом всей анимации.
- Проблемы с отменой: При отмене жеста анимации "прыгали" в исходное состояние.
// Проблемный код UIView.animate(withDuration: 0.3, animations: { backgroundView.alpha = 0 }) UIView.animate(withDuration: 0.5, delay: 0.1, options: .curveEaseOut, animations: { imageView.transform = CGAffineTransform(scaleX: 0.8, y: 0.8) }, completion: nil) // Нет связи между анимациями
Решение:
- Использование
UIViewPropertyAnimator:- Создал один общий аниматор с кастомной timing curve.
- Добавил все анимации свойств в один блок
addAnimations. - Это гарантировало их полную синхронизацию.
- Интеграция с жестом
UIPanGestureRecognizer:- Аниматор был создан в состоянии
.inactive. - В обработчике жеста значение
fractionCompleteаниматора обновлялось пропорционально смещению пальца. - При завершении жеста аниматор либо завершался (
startAnimation()), либо откатывался (isReversed = true).
- Аниматор был создан в состоянии
- Оптимизация производительности:
- Для сложных вычислений позиции (например, параллакс-эффект) использовал
CADisplayLink, чтобы обновлятьfractionCompleteна каждом кадре.
- Для сложных вычислений позиции (например, параллакс-эффект) использовал
Итоговый вывод: Для сложных, интерактивных анимаций необходимо использовать низкоуровневые инструменты (UIViewPropertyAnimator, CADisplayLink), которые предоставляют точный контроль над временем и прогрессом, вместо простых блоковых анимаций.