Не так давно на WWDC 2016 был анонсирован обновленный интерфейс работы с интерактивными анимациями в iOS 10: теперь у разработчиков появился гибкий инструмент их создания, управления и модификации. В этой статье речь пойдет о том, какие произошли изменения и что из себя представляет новое API.

Центральным классом нового API является UIViewPropertyAnimator, предоставляющий свой функционал на основе двух протоколов UIViewAnimating и UIViewImplicitlyAnimating, которые будут рассмотрены ниже.

UIViewPropertyAnimator


С помощью данного класса можно:

  • создавать интерактивные анимации (их можно останавливать, ставить на паузу, прерывать, обрабатывать касания пользователя);
  • модифицировать уже запущенные;
  • перематывать текущую анимацию на любой момент;
  • проигрывать в обратном порядке;
  • настраивать timing functions.

Timing functions
Под timing functions (или easing functions) понимаются функции скорости анимации, которые влияют на темп изменения того или иного анимируемого свойства. Сейчас поддерживается четыре типа: easeInOut, easeIn, easeOut, linear.

Пример использования:

        // Скорость анимации
        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 (при вызове выставляет fraction для анимации) и UITapGestureRecognizer (при тапе анимация проигрывается).

Сначала тянем иконку вправо и вручную проматываем анимацию (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), то есть в финальном состоянии, как будто анимация уже кончилась.

Пример
Для этой анимации выставлен isManualHitTestingEnabled = true. На иконку добавлен UITapGestureRecognizer (при тапе текст лейбла внизу изменится на Received touch). Как видно, иконка в движении не получает касания пользователя, но если тапнуть на предполагаемое место окончания анимации (самая правая часть поля), селектор сработает, как будто иконка находится именно там.



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

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.

Поделиться с друзьями
-->

Комментарии (2)


  1. grayich
    01.08.2016 16:37
    -4

    На завлекательной анимации тень и блик неправильные.
    А ведь подобные анимации могут откладываться паттернами в детских и других несформированных разумах. К чему это может привести?


  1. eGorets
    02.08.2016 22:09
    +1

    спасибо