CATransaction: представление мигает по завершении

Я пишу немного сложную анимацию, которая состоит из 2 шагов:

  1. Измените непрозрачность на 0 из UIViews, которые не должны быть видимыми, и переместите UIImageView (который имеет alpha = 1) в другое CGPoint (положение).
  2. Измените непрозрачность другого UIView на 1 и непрозрачность UIImageView из предыдущего шага на 0, а затем после завершения анимации этого шага удалите UIImageView из супервизора.

Я сделал это так:

Первый шаг выполняется без явной CATransaction. Для этих двух анимаций beginTime установлено значение CACurrentMediaTime(). И я применяю изменения к представлениям сразу после layer.addAnimation(...) вызова. Здесь все отлично работает.

В реализации второго шага я вызываю CATransaction.begin() в начале. Внутри begin/commit вызовов CATransaction я создаю и добавляю 2 CABasicAnimations к двум разным слоям: один для изменения непрозрачности с 0 на 1 (для UIView), а второй для изменения непрозрачности с 1 на 0 (для UIImageView). Для обеих анимаций beginTime установлено значение CACurrentMediaTime() + durationOfThePreviousStep.

И сразу после CATransaction.begin() я вызываю CATransaction.setCompletionBlock({...}), и в этом блоке завершения я применяю изменения к этим двум представлениям: устанавливаю их новые альфы и удаляю UIImageView из супервизора.

Проблема в том, что в конце всей этой анимации UIView с альфа-анимацией до 1 мигает, что означает, что его альфа возвращается к 0 (хотя я установил его альфа в 1 в блоке завершения) и сразу после этого завершение блок выполняется, и его альфа снова увеличивается до 1.

Ну и вопрос, как избавиться от этой перепрошивки? Может, эту анимацию можно сделать лучше?

P.S. Я не использую UIView анимацию, потому что меня интересуют специальные функции времени для этих анимаций.

ИЗМЕНИТЬ 1: вот код. Я удалил UIImageView альфа-анимацию, потому что в этом нет особой необходимости.

var totalDuration: CFTimeInterval = 0.0

// Alpha animations.
let alphaAnimation = CABasicAnimation()
alphaAnimation.keyPath = "opacity"
alphaAnimation.fromValue = 1
alphaAnimation.toValue = 0
alphaAnimation.beginTime = CACurrentMediaTime()
alphaAnimation.duration = 0.15

let alphaAnimationName = "viewsFadeOut"
view1.layer.addAnimation(alphaAnimation, forKey: alphaAnimationName)
view1.alpha = 0

view2.layer.addAnimation(alphaAnimation, forKey: alphaAnimationName)
view2.alpha = 0

view3.layer.addAnimation(alphaAnimation, forKey: alphaAnimationName)
view3.alpha = 0

view4.layer.addAnimation(alphaAnimation, forKey: alphaAnimationName)
view4.alpha = 0

// Image View moving animation.
// Add to total duration.
let rect = /* getting rect */
let newImagePosition = view.convertPoint(CGPoint(x: CGRectGetMidX(rect), y: CGRectGetMidY(rect)), fromView: timeView)

let imageAnimation = CABasicAnimation()
imageAnimation.keyPath = "position"
imageAnimation.fromValue = NSValue(CGPoint: imageView!.layer.position)
imageAnimation.toValue = NSValue(CGPoint: newImagePosition)
imageAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionDefault)
imageAnimation.beginTime = CACurrentMediaTime()
imageAnimation.duration = 0.3

imageView!.layer.addAnimation(imageAnimation, forKey: "moveImage")
imageView!.center = newImagePosition

totalDuration += imageAnimation.duration

// Time View alpha.
CATransaction.begin()
CATransaction.setCompletionBlock {
    self.timeView.alpha = 1
    self.imageView!.removeFromSuperview()
    self.imageView = nil
}

let beginTime = CACurrentMediaTime() + totalDuration
let duration = 0.3

alphaAnimation.fromValue = 0
alphaAnimation.toValue = 1
alphaAnimation.beginTime = beginTime
alphaAnimation.duration = duration
timeView.layer.addAnimation(alphaAnimation, forKey: "timeViewFadeIn")

/* imageView alpha animation is not necessary, so I removed it */

CATransaction.commit()

ИЗМЕНИТЬ 2: фрагмент кода, вызывающий проблему:

CATransaction.begin()
CATransaction.setCompletionBlock {
    self.timeView.alpha = 1
}

let duration = 0.3

let alphaAnimation = CABasicAnimation()
alphaAnimation.keyPath = "opacity"
alphaAnimation.fromValue = 0.0
alphaAnimation.toValue = 1.0
alphaAnimation.duration = duration
timeView.layer.addAnimation(alphaAnimation, forKey: "timeViewFadeIn")

CATransaction.commit()

Возможно, проблема в том, что timeView имеет UITextField и UIScrollView с четырьмя подпредставлениями. Я пытался заменить timeView снимком timeView (UIImageView), но это не помогло.

РЕДАКТИРОВАТЬ 3: новый код. В этом коде timeView всегда имеет alpha = 1, и он также анимирует от 0 до 1.

CATransaction.begin()
CATransaction.setCompletionBlock {
    self.imageView!.removeFromSuperview()
    self.imageView = nil
}

let alphaAnimation = CABasicAnimation()
alphaAnimation.keyPath = "opacity"
alphaAnimation.fromValue = 0.0
alphaAnimation.toValue = 1.0
alphaAnimation.duration = 0.3
alphaAnimation.beginTime = beginTime

timeView.layer.addAnimation(alphaAnimation, forKey: "timeViewFadeIn")
timeView.alpha = 1.0
CATransaction.commit()

person Randex    schedule 24.05.2015    source источник
comment
Пожалуйста, покажите свой код или достаточно похожий / сокращенный код, чтобы упростить воспроизведение проблемы.   -  person matt    schedule 24.05.2015
comment
Ну а какая мигает? Можем ли мы исключить код для других анимаций?   -  person matt    schedule 24.05.2015
comment
timeView - это тот, который мигает. Да, вы можете удалить другой код, но мне это не помогло. Он все еще мигает.   -  person Randex    schedule 24.05.2015
comment
Да, но не это причина для его устранения. Причина для его устранения в том, что мне нужно что-то минимальное, что я могу понять и, при необходимости, воспроизвести в Xcode. Можете ли вы урезать мне пример кода? Я не хочу создавать четыре дополнительных представления только для запуска вашего кода.   -  person matt    schedule 24.05.2015
comment
Отлично, и я думаю, что теперь понимаю источник проблемы; смотри мой ответ.   -  person matt    schedule 24.05.2015


Ответы (1)


Просто глядя на ваш код, я ожидал, что он мигнет. Почему? Поскольку вы изменили непрозрачность слоя timeView от 0 до 1, но не установили значение 1 (за исключением обработчика завершения, который произойдет позже). Таким образом, мы анимируем слой презентации от 0 до 1, а затем анимация заканчивается, и выясняется, что непрозрачность реального слоя всегда была равна 0.

Итак, установите непрозрачность слоя timeView на 1, прежде чем ваша анимация начнется. Кроме того, поскольку вы используете отложенный beginTime, вам нужно будет установить fillMode вашей анимации на "backwards".

Мне удалось получить хорошие результаты, изменив ваш тестовый код, сделав его самодостаточным и похожим на это; есть задержка, вид бледнеет, а в конце нет вспышки:

    CATransaction.begin()
    let beginTime = CACurrentMediaTime() + 1.0 // arbitrary, just testing
    let alphaAnimation = CABasicAnimation()
    alphaAnimation.keyPath = "opacity"
    alphaAnimation.fromValue = 0.0
    alphaAnimation.toValue = 1.0
    alphaAnimation.duration = 1.0 // arbitrary, just testing
    alphaAnimation.fillMode = "backwards"
    alphaAnimation.beginTime = beginTime
    timeView.layer.addAnimation(alphaAnimation, forKey: "timeViewFadeIn")
    timeView.layer.opacity = 1.0
    CATransaction.commit()

В вашем коде есть еще кое-что, что я считаю довольно странным. Использование блока завершения транзакции таким способом несколько рискованно; Я не понимаю, почему вы не передаете свою анимацию делегату. Кроме того, вы многократно используете alphaAnimation; Я не могу этого рекомендовать. На вашем месте я бы создал новый CABasicAnimation для каждой анимации.

person matt    schedule 24.05.2015
comment
Спасибо! Но почему вы не можете рекомендовать повторно использовать CABasicAnimations? - person Randex; 24.05.2015
comment
let duration = 0.3 - да, забыл вставить эту строчку, когда редактировал вопрос: alphaAnimation.duration = duration - person Randex; 24.05.2015
comment
Но почему вы не можете рекомендовать повторно использовать CABasicAnimations? Это кажется ленивым и ненужным. К тому же это немного рискованно. Бывает, что CABasicAnimation копируется, когда он назначается слою, поэтому ваши последующие изменения не влияют на ранее назначенные анимации, а это всего лишь деталь реализации. В общем случае экземпляр класса следует рассматривать как экземпляр класса - он изменяемый, и изменения влияют на все ссылки на него. - person matt; 24.05.2015
comment
Спасибо за объяснение. У меня с этой перепрошивкой проблем больше. Я удалил настройку альфа из блока завершения. Теперь, если я установлю его сразу после вызова addAnimation::, timeView будет с альфа = 1 от начала всей анимации, и он будет анимировать ее от 0 до 1 в конце. Если я вместо этого установлю делегата для этой анимации, она будет мигать, как раньше. - person Randex; 24.05.2015
comment
Я считаю, что то, что я говорю, правильно, исходя из кода, который вы показали. Я не знаю, какие изменения вы внесли в свой код, поэтому я не знаю, что нового вы делаете неправильно. - Я предлагаю вам начать все сначала с небольшого нового проекта и просто доказать себе, что можно анимировать альфа-канал вида от 0 до 1, а затем удалить его без промежуточной вспышки. Как только вы узнаете, как это сделать, вы можете вернуться к своему реальному коду и сделать это там же. - person matt; 24.05.2015
comment
Пожалуйста, взгляните на Edit 3 вопроса. Кроме того, я уже делал альфа-анимацию без прошивки, но на этот раз она просто не работает. Может быть, потому что я использую beginTime для упорядочивания своих анимаций? - person Randex; 24.05.2015
comment
Извините, я все еще не понимаю. Вы по-прежнему имеете в виду два разных взгляда: self.imageView и timeView. Нужны ли мне оба из них, чтобы воспроизвести проблему? - person matt; 24.05.2015
comment
Ой, извини. Нет, вам нужно только timeView. - person Randex; 24.05.2015
comment
Добавьте эту строку: alphaAnimation.fillMode = "backwards" - добавил эту информацию к моему ответу. - person matt; 24.05.2015
comment
Добавлен рабочий код. Это то, что я имею в виду, сокращая его до небольшого проекта. Это весь код, который вам нужен, чтобы убедиться, что это возможно. Теперь просто адаптируйте его к вашей реальной ситуации. Этот процесс устранения всего лишнего и сосредоточения внимания на фактическом источнике проблемы - это способ отладки в реальной жизни. - person matt; 24.05.2015
comment
Большое тебе спасибо! Я добавил эту строку, и она сработала, хотя я не очень понимаю, как это работает kCAFillModeBackwards. Документация об этом не совсем ясна. - person Randex; 24.05.2015
comment
Что ж, вы были совершенно правы: это все из-за beginTime. Нам нужно, чтобы fromValue вступил в силу с самого начала; это то, что делает режим заполнения. В противном случае fromValue не применяется до прибытия beginTime. - person matt; 24.05.2015