iOS 10: новое в создании анимаций
Не так давно на WWDC 2016 был анонсирован обновленный интерфейс работы с интерактивными анимациями в iOS 10: теперь у разработчиков появился гибкий инструмент их создания, управления и модификации. В этой статье речь пойдет о том, какие произошли изменения и что из себя представляет новое API.
Центральным классом нового API является UIViewPropertyAnimator, предоставляющий свой функционал на основе двух протоколов UIViewAnimating и UIViewImplicitlyAnimating, которые будут рассмотрены ниже.
UIViewPropertyAnimator
С помощью данного класса можно:
- создавать интерактивные анимации (их можно останавливать, ставить на паузу, прерывать, обрабатывать касания пользователя);
- модифицировать уже запущенные;
- перематывать текущую анимацию на любой момент;
- проигрывать в обратном порядке;
- настраивать timing functions.
Пример использования:
// Скорость анимации
let parameters = UICubicTimingParameters(animationCurve: .easeIn)
// Конфигурирование аниматора с учетом длительности и скорости
let animator = UIViewPropertyAnimator(duration: 5.0, timingParameters: parameters)
// Набор анимаций (как во всем знакомом UIView.animate(withDuration:, animations:))
animator.addAnimations {
view.center = CGPoint(x: 150, y: 200)
view.transform = CGAffineTransform(rotationAngle: CGFloat(M_PI_2))
view.backgroundColor = UIColor.red()
}
// Completion block
animator.addCompletion { _ in
view.backgroundColor = UIColor.orange()
}
// Запуск проигрывания анимации
animator.startAnimation()
Может показаться, что такая реализация более многословна по сравнению со старым API, зато теперь есть аниматор, который можно реиспользовать/изменять/сохранять и наслаждаться остальными преимуществами наличия цельного объекта.
Также Apple предоставляет новые протоколы:
UIViewAnimating
Этот протокол отвечает за основные действия, которые мы можем выполнить с аниматором: запуск, пауза, остановка, перемотка, получение текущего состояния и позиции анимации. Больше информации можно получить в документации. Остановимся на паре методов и свойств чуть подробнее: Перемотка анимации (scrubbing)
var fractionComplete: CGFloat { get set }
animator.fractionComplete = fraction
С помощью такого свойства можно поэтапно продвигаться по анимации вперед/назад, получая ее состояние в выбранный момент времени. Благодаря такой возможности пользователь может взаимодействовать с интерфейсом как с чем-то живым и интерактивным.
Сначала тянем иконку вправо и вручную проматываем анимацию (UIPanGestureRecognizer). Затем тапаем по иконке и смотрим на анимацию, которая проигрывается сама (UITapGestureRecognizer).
func finishAnimation(at: UIViewAnimatingPosition)
Входной параметр at показывает, в какой позиции полностью завершится анимация.
Варианты значений enum UIViewAnimatingPosition:
- start;
- end;
- current (текущая позиция — любая точка цикла анимации).
Параметр позиции может оказаться очень полезным, когда потребуется закончить анимацию в нестандартном состоянии. Например, в середине цикла или на последнем кадре.(Временная) остановка анимации
func stopAnimation(_ withoutFinishing: Bool)
Параметр withoutFinishing позволяет либо остановить анимацию полностью (как при использовании finishAnimation), либо перевести в состояние stopped, из которого анимацию можно продолжить с текущего места. Состояние анимации
- inactive
Начальное состояние — каждый новый созданный объект-аниматор начинает с него. После завершения анимации (finishAnimation) он возвращается к этому же состоянию. - active
Аниматор переходит в такое состояние после вызовов startAnimation () или pauseAnimation (). - stopped
Состояние аниматора после вызова stopAnimation, где withoutFinishing == false.
Наглядное изображение изменения состояния:
И еще несколько интересных свойств UIViewAnimating:
// Можно ли анимацию ставить на паузу или останавливать
var isInterruptible: Bool
// Способ обработки тачей (по умолчанию false)
var isManualHitTestingEnabled: Bool
При значении по умолчанию анимируемый объект будет получать все касания пользователя. Если выставить isManualHitTestingEnabled = true, то все тачи будет получать не объект в своем текущем состоянии (presentation layer), а его модельный слой (model layer), то есть в финальном состоянии, как будто анимация уже кончилась.
UIViewImplicitlyAnimating
Этот протокол наследуется от предыдущего (UIViewAnimating) и предоставляет важную часть функционала: добавление анимаций через блок и создание completion block.
Одна из новых возможностей — это продолжение анимации после паузы или остановки, причем с изменением timing function.
let newParameters = UICubicTimingParameters(animationCurve: .easeOut)
animator.continueAnimation(withTimingParameters: newParameters, durationFactor: 2.0)
Это может пригодиться, если нужно изменить темп анимации после взаимодействия с пользователем (остановки или паузы анимации).
UITimingCurveProvider
Используется при создании объекта UIViewPropertyAnimator для установки timing function через UISpringTimingParameters или UICubicTimingParameters.UICubicTimingParameters
Сейчас UIKIt дает нам только четыре старые добрые функции скорости (easeInOut, easeIn, easeOut, linear).
Но в новом API появился простор для фантазии дизайнеров — возможность создавать свои timing functions по двум контрольным точкам:
let controlPoint1 = CGPoint(x: 0.2, y: 0.1)
let controlPoint2 = CGPoint(x: 0.8, y: 0.8)
let parameters = UICubicTimingParameters(controlPoint1: controlPoint1, controlPoint2: controlPoint2)
animator = UIViewPropertyAnimator(duration: 0.5, timingParameters: parameters)
UISpringTimingParametersЗдесь интересным моментом является то, что скорость теперь вектор, а не скаляр. Раньше скорость была направлена вдоль линии в отличие от нового API, где расчет идет по двум осям, а значит, анимация выглядит более натурально.
А еще Apple открыли для использования spring equation, то есть теперь можно устанавливать любые коэффициенты и настройки для UISpringTimingParameters, но надо учитывать, что в этом случае длительность игнорируется и высчитывается соответственно параметрам:
// Spring equation
init(mass: CGFloat, stiffness: CGFloat, damping: CGFloat, initialVelocity velocity: CGVector)
Заключение
После просмотра лекции и нескольких примеров кода остается общее впечатление, что добавилось разнообразие и возможности для экспериментов с timing functions и модификацией анимаций, а API стало более многословным, но гибким. Будем надеяться, разработчикам теперь станет еще проще и приятнее добавлять интерактив в свои приложения!
Подробнее познакомиться с примерами можно в лекции Advances in UIKit Animations and Transitions.