Как синхронно анимировать UIView и CALayer

Чтобы проиллюстрировать этот вопрос, я разместил на Github очень небольшой проект Xcode (два класса, загрузка 11 КБ). Взгляните на суть здесь или используйте git clone [email protected]: 93982af3b65d2151672e .git.


Пожалуйста, рассмотрите следующий сценарий. Мой собственный вид, который называется 'container view', содержит два маленьких квадраты. Иллюстрировано на этом скриншоте:

Скриншот исходного положения двух квадратов

Синий квадрат - это экземпляр размером 22x22 pt UIView, а красный квадрат - это экземпляр размером 22x22 pt CALayer. Для целей этого вопроса я хочу, чтобы два квадрата «прилипали» к нижнему правому углу внешнего вида, в то время как я анимирую рамку этого внешнего вида.

Я изменяю фрейм представления контейнера в UIView методе класса animateWithDuration:delay:options:animations:completion: с нестандартным параметром замедления типа UIViewAnimationCurveEaseOut.

[UIView animateWithDuration:1.0 delay:0 
                    options:UIViewAnimationCurveEaseOut 
                 animations:^{ _containerView.frame = randomFrame; } 
                 completion:nil];

Примечание. И синий UIView, и красный фрейм CALayer устанавливаются в переопределенном методе setFrame: представления контейнера, но результаты были бы такими же, если бы я установил для синего свойства UIView autoresizingMask значение UIView flexible left и верхние поля.

В результирующей анимации синий квадрат «прилипает» к углу именно так, как я задумал, но красный квадрат полностью игнорирует как время, так и замедление анимации. Я предполагаю, что это из-за функции неявной анимации в Core Animation, функции, которая раньше помогала мне во многих случаях.

Вот несколько снимков экрана в анимационной последовательности, которые иллюстрируют асинхронность двух квадратов:

Три скриншота в середине анимации, где красный квадрат находится не в« правильном »месте»></p>

<p>Переходим к вопросу: <strong>Есть ли способ синхронизировать смену двух кадров, чтобы красный CALayer и синий UIView перемещались с одинаковой продолжительностью анимации и кривой плавности, прилипая друг к другу, как если бы они были одним видом? <  / сильный></p>

<p>P.S.  Конечно, требуемый визуальный результат склеивания двух квадратов может быть достигнут любым количеством способов, например, если оба слоя станут либо <code>CALayer</code>s, либо <code>UIView</code>s, но проект, в котором находится реальная проблема, имеет очень законную причину для одного.  быть CALayer, а другой - UIView.</p>
    </div>

    <br>

    <div class=

person epologee    schedule 29.05.2012    source источник


Ответы (2)


Я предполагаю, что вы правильно указали конечное положение и что после анимации вид и слой выровнены. Это не имеет ничего общего с анимацией, это просто геометрия.


Когда вы изменяете свойство CALayer , которое не является слоем представления (как в вашем случае), оно неявно анимируется с новым значением. Чтобы настроить эту анимацию, вы можете использовать явную анимацию, например базовую анимацию. Изменение кадра в базовой анимации будет выглядеть примерно так.

CABasicAnimation *myAnimation = [CABasicAnimation animationWithKeyPath:@"frame"];
[myAnimation setToValue:[NSValue valueWithCGRect:myNewFrame]];
[myAnimation setFromValue:[NSValue valueWithCGRect:[myLayer frame]]];
[myAnimation setDuration:1.0];
[myAnimation setTimingFunction:[CAMediaTimingFuntion functionWithName: kCAMediaTimingFunctionEaseOut]];

[myLayer setFrame:myNewFrame];
[myLayer addAnimation:myAnimation forKey:@"someKeyForMyAnimation"];

Если функция синхронизации и продолжительность обеих анимаций одинаковы, они должны оставаться выровненными.

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


Да, есть несколько способов добиться того же эффекта. Один из примеров - представление и слой, являющиеся подпредставлениями одного и того же подпредставления (которое, в свою очередь, является подпредставлением внешнего кадра).

Редактировать

Вы не можете легко сгруппировать UIView-анимацию с явной анимацией. Вы также не можете использовать группу анимации (поскольку вы применяете их к разным слоям), но вы можете использовать CATransaction:

[CATransaction begin];
[CATransaction setAnimationDuration:1.0];
[CATransaction setAnimationTimingFunction:[CAMediaTimingFuntion functionWithName: kCAMediaTimingFunctionEaseOut]];

// Layer animation
CABasicAnimation *myAnimation = [CABasicAnimation animationWithKeyPath:@"frame"];
[myAnimation setToValue:[NSValue valueWithCGRect:myNewFrame]];
[myAnimation setFromValue:[NSValue valueWithCGRect:[myLayer frame]]];

[myLayer setFrame:myNewFrame];
[myLayer addAnimation:myAnimation forKey:@"someKeyForMyAnimation"];

// Outer animation
CABasicAnimation *outerAnimation = [CABasicAnimation animationWithKeyPath:@"frame"];
[outerAnimation setToValue:[NSValue valueWithCGRect:myNewOuterFrame]];
[outerAnimation setFromValue:[NSValue valueWithCGRect:[outerView frame]]];

[[outerView layer] setFrame:myNewOuterFrame];
[[outerView layer] addAnimation:outerAnimation forKey:@"someKeyForMyOuterAnimation"];

[CATransaction commit];
person David Rönnqvist    schedule 29.05.2012
comment
Ваше главное предположение верно, квадраты всегда заканчиваются так, как должны, но, хотя использование CABasicAnimation немного меняет анимацию, это далеко не синхронно. Я почти подозреваю, что две функции ослабления Core Animation и UIView отличаются (и в вашем CAMediaTimingFunCtion есть небольшая опечатка). Вы можете опробовать это в представленном проекте. - person epologee; 29.05.2012
comment
Нет ли возможности изменить кадры UIView и CALayer за один раз? Нижнее предложение не работает для «реальной проблемы», поскольку CALayer только меняет положение, но также меняет размер UIView. - person epologee; 29.05.2012
comment
@ DavidRönnqvist Это второй раз за сегодня, когда ваш ответ был невероятно полезным для меня ... THX! - person Lindemann; 06.07.2013

Если вам нужен фрагмент кода Дэвида Рённквиста в Swift 3.0, вот он:

    CATransaction.begin()
    CATransaction.setAnimationDuration(1.0)
    CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut))

    // Layer animation
    let myAnimation = CABasicAnimation(keyPath: "frame");
    myAnimation.toValue = NSValue(cgRect: myNewFrame)
    myAnimation.fromValue = NSValue(cgRect: myLayer.frame)

    myLayer.frame = myNewFrame
    myLayer.add(myAnimation, forKey: "someKeyForMyAnimation")

    // Outer animation
    let outerAnimation = CABasicAnimation(keyPath: "frame")
    outerAnimation.toValue = NSValue(cgRect: myNewOuterFrame)
    outerAnimation.fromValue = NSValue(cgRect: outerView.frame)

    outerView.layer.frame = myNewOuterFrame
    outerView.layer.add(outerAnimation, forKey: "someKeyForMyOuterAnimation")

    CATransaction.commit()
person Juan Fran Jimenez    schedule 07.07.2017