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

To illustrate this question, I gisted a very small Xcode project on Github (two classes, 11kb download). Посмотрите на суть здесь или использоватьgit clone [email protected]:93982af3b65d2151672e.git.

Пожалуйста, рассмотрите следующий сценарий. Мой собственный вид, называемый«вид контейнера», содержит две маленькие квадраты. На этом скриншоте показано:

Screenshot of the initial position of the two squares

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

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

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

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

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

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

Three screenshots mid-animation where the red square is not in the 'right' place

На вопрос:Is there a way to synchronize the two frame changes so that the red CALayer and the blue UIView both move with the same animation duration and easing curve, sticking to each other as if they were one view?

Постскриптум Конечно, требуемый визуальный результат слипания двух квадратов может быть достигнут любым количеством способов, например, если оба слоя станутCALayerс илиUIViews, но проект, в котором находится реальная проблема, имеет вполне законное основание для того, чтобы один был CALayer, а другой - UIView.

Ответы на вопрос(2)

Решение Вопроса

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

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

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"];

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

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

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

Edit

Вы не можете легко сгруппировать 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];
 epologee29 мая 2012 г., 17:06
Ваше верхнее предположение верно, квадраты всегда заканчиваются так, как должны, но хотя использование CABasicAnimation немного меняет анимацию, она почти не синхронна. Я почти подозреваю, что две функции замедления Core Animation и UIView различны (и в вашейCAMediaTimingFunCtion). Вы можете попробовать это в проекте.
 06 июл. 2013 г., 03:23
@ DavidR & # xF6; nnqvist Сегодня уже во второй раз ваш ответ был невероятно полезным для меня ... THX!
 29 мая 2012 г., 20:29
@epologee Я обновил свой ответ. Это работает для вас?
 epologee29 мая 2012 г., 17:07
Нет ли способа изменить оба фрейма UIView и CALayer за один раз? Нижнее предложение не работает для «реальной проблемы», поскольку CALayer только перемещает, но также изменяет размер UIView.

Если вы хотите, чтобы фрагмент Дэвида R'nnvvist в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()

Ваш ответ на вопрос