Почему UIView.animate работает с переходом интерактивного контроллера, а UIViewPropertyAnimator - нет?
Образец настройки распознавателя жестов и тому подобного для интерактивного перехода см. В этом ответе.
Я экспериментирую с интерактивными переходами и потратил немало времени, пытаясь выяснить, почему контроллеры будут переходить нормально, а не чистить в соответствии с жестом. Я обнаружил, что это не работает, потому что я использую UIViewPropertyAnimator
, Переключение на более старые блоки анимации UIView работает из коробки. Зачем? Какая разница в реализации?
func animateTransition(using transitionContext: UIViewControllerContextTransitioning)
{
// Ignore the forced unwrapping, for sake of brevity.
let view_From = transitionContext.viewController(forKey: .from)!.view!
let view_To = transitionContext.viewController(forKey: .to)!.view!
transitionContext.containerView.insertSubview(view_To, aboveSubview: view_From)
view_To.alpha = 0
// This animation block works - it will follow the progress value of the interaction controller
UIView.animate(withDuration: 1, animations: {
view_From.alpha = 0.0
view_To.alpha = 1.0
}, completion: { finished in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
// This animation block fails - it will play out normally and not be interactive
/*
let animator = UIViewPropertyAnimator(duration: 1, curve: .linear)
animator.addAnimations {
view_To.alpha = 1
view_From.alpha = 0
}
animator.addCompletion { (position) in
switch position {
case .end: print("Completion handler called at end of animation")
case .current: print("Completion handler called mid-way through animation")
case .start: print("Completion handler called at start of animation")
}
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
animator.startAnimation()
*/
}
1 ответ
С введением UIViewPropertyAnimator
в iOS 10 UIViewControllerAnimatedTransitioning
Протокол тоже обновился. Они добавили необязательный func interruptibleAnimator(using: UIViewControllerContextTransitioning)
что вам не нужно реализовывать (думаю, для обратной совместимости). Но он был добавлен именно для того случая использования, который вы упомянули здесь: чтобы воспользоваться преимуществами нового UIViewPropertyAnimator
,
Итак, чтобы получить то, что вы хотите: во-первых, вы должны реализовать interruptibleAnimator(using:)
создать аниматора - вы не создадите его в animateTransition(using:)
,
Согласно комментарию в исходном коде UIViewControllerAnimatedTransitioning
(акцент мой)(я понятия не имею, почему документация не содержит эту информацию):
Соответствующий объект реализует этот метод, если создаваемый им переход может быть прерван. Например, он мог бы вернуть экземпляр UIViewPropertyAnimator. Ожидается, что этот метод будет возвращать один и тот же экземпляр в течение жизни перехода.
Вы должны вернуть тот же аниматор на время перехода. Вот почему вы найдете
private var animatorForCurrentSession: UIViewImplicitlyAnimating?
собственность в моем BackAnimator
реализация - я сохраняю текущий аниматор, чтобы вернуть его, если переход еще не закончился.
Когда interruptibleAnimator(using:)
реализуется, среда возьмет этот аниматор и использует его вместо анимации, используя animateTransition(using:)
, Но чтобы сохранить договор протокола, animateTransition(using:)
должен быть в состоянии оживить переход - но вы можете просто использовать interruptibleAnimator(using:)
создать аниматор и запустить анимацию там.
Следующий рабочий BackAnimator
реализация, которую вы можете использовать с примером, который вы упомянули в этом вопросе SO. Я использовал ваш код в качестве основы, но вы можете просто поменять мой BackAnimator
для их реализации, и вы готовы пойти (я проверял это на их примере).
class BackAnimator : NSObject, UIViewControllerAnimatedTransitioning {
// property for keeping the animator for current ongoing transition
private var animatorForCurrentTransition: UIViewImplicitlyAnimating?
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.5
}
func interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating {
// as per documentation, the same object should be returned for the ongoing transition
if let animatorForCurrentSession = animatorForCurrentTransition {
return animatorForCurrentSession
}
// normal creation of the propertyAnimator
let view_From = transitionContext.viewController(forKey: .from)!.view!
let view_To = transitionContext.viewController(forKey: .to)!.view!
transitionContext.containerView.insertSubview(view_To, aboveSubview: view_From)
view_To.alpha = 0
let animator = UIViewPropertyAnimator(duration: transitionDuration(using: transitionContext), curve: .linear)
animator.addAnimations {
view_To.alpha = 1
view_From.alpha = 0
}
animator.addCompletion { (position) in
switch position {
case .end: print("Completion handler called at end of animation")
case .current: print("Completion handler called mid-way through animation")
case .start: print("Completion handler called at start of animation")
}
// transition completed, reset the current animator:
self.animatorForCurrentTransition = nil
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
// keep the reference to current animator
self.animatorForCurrentTransition = animator
return animator
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
// animateTransition should work too, so let's just use the interruptibleAnimator implementation to achieve it
let anim = self.interruptibleAnimator(using: transitionContext)
anim.startAnimation()
}
}
Также обратите внимание, что аниматор вернулся interruptibleAnimator(using:)
не запускается нами - среда запускает его, когда это необходимо.
PS: Большая часть моих знаний по этому вопросу происходит от попыток реализовать контейнер с открытым исходным кодом, который бы позволял настраивать интерактивные переходы между его контрагентами - https://github.com/MilanNosal/InteractiveTransitioningContainer. Может быть, вы тоже найдете там вдохновение:).