IT박스

애니메이션이 완료된 후 CABasicAnimation이 초기 값으로 재설정

itboxs 2020. 6. 30. 20:59
반응형

애니메이션이 완료된 후 CABasicAnimation이 초기 값으로 재설정


CALayer를 회전시키고 애니메이션이 완료된 후 최종 위치에서 중지하려고합니다.

그러나 애니메이션이 완료되면 초기 위치로 재설정됩니다.

(xcode 문서는 애니메이션이 속성 값을 업데이트하지 않는다고 명시 적으로 말합니다.)

이것을 달성하는 방법에 대한 제안.


여기에 답이 있습니다. 제 대답과 Krishnan의 조합입니다.

cabasicanimation.fillMode = kCAFillModeForwards;
cabasicanimation.removedOnCompletion = NO;

기본값은 kCAFillModeRemoved입니다. 보고있는 재설정 동작은 무엇입니까?


removedOnCompletion의 문제점은 UI 요소가 사용자 상호 작용을 허용하지 않는다는 것입니다.

기술은 애니메이션의 FROM 값과 객체의 TO 값을 설정하는 것입니다. 애니메이션은 시작하기 전에 TO 값을 자동으로 채우며, 제거하면 오브젝트가 올바른 상태로 유지됩니다.

// fade in
CABasicAnimation *alphaAnimation = [CABasicAnimation animationWithKeyPath: @"opacity"];
alphaAnimation.fillMode = kCAFillModeForwards;

alphaAnimation.fromValue = NUM_FLOAT(0);
self.view.layer.opacity = 1;

[self.view.layer addAnimation: alphaAnimation forKey: @"fade"];

다음 속성을 설정하십시오.

animationObject.removedOnCompletion = NO;

그냥 코드 안에 넣으십시오

CAAnimationGroup *theGroup = [CAAnimationGroup animation];

theGroup.fillMode = kCAFillModeForwards;

theGroup.removedOnCompletion = NO;

당신은 단순히의 주요 설정할 수 있습니다 CABasicAnimationposition당신이 레이어에 추가 할 때입니다. 이렇게하면 실행 루프의 현재 패스 위치에서 수행 된 암시 적 애니메이션이 무시됩니다.

CGFloat yOffset = 30;
CGPoint endPosition = CGPointMake(someLayer.position.x,someLayer.position.y + yOffset);

someLayer.position = endPosition; // Implicit animation for position

CABasicAnimation * animation =[CABasicAnimation animationWithKeyPath:@"position.y"]; 

animation.fromValue = @(someLayer.position.y);
animation.toValue = @(someLayer.position.y + yOffset);

[someLayer addAnimation:animation forKey:@"position"]; // The explicit animation 'animation' override implicit animation

2011 Apple WWDC 비디오 세션 421-Core Animation Essentials (비디오 중간)에 대한 자세한 정보를 얻을 수 있습니다.


CALayer에는 모델 계층과 프레젠테이션 계층이 있습니다. 애니메이션 중 프레젠테이션 레이어는 모델과 독립적으로 업데이트됩니다. 애니메이션이 완료되면 프리젠 테이션 레이어가 모델의 값으로 업데이트됩니다. 애니메이션이 끝난 후 삐걱 거리는 점프를 피하려면 중요한 것은 두 레이어의 동기화를 유지하는 것입니다.

최종 값을 알고 있다면 모델을 직접 설정할 수 있습니다.

self.view.layer.opacity = 1;

그러나 끝 위치를 모르는 애니메이션이있는 경우 (예 : 사용자가 일시 정지 한 후 반전 할 수있는 느린 페이드) 프레젠테이션 레이어를 직접 쿼리하여 현재 값을 찾은 다음 모델을 업데이트 할 수 있습니다.

NSNumber *opacity = [self.layer.presentationLayer valueForKeyPath:@"opacity"];
[self.layer setValue:opacity forKeyPath:@"opacity"];

프리젠 테이션 레이어에서 값을 가져 오는 것도 스케일링 또는 회전 키 패스에 특히 유용합니다. (예를 들어 transform.scale, transform.rotation)


단순히 설정 fillMode하고 removedOnCompletion나에게 효과가 없었습니다. 아래의 모든 속성을 CABasicAnimation 객체 로 설정하여 문제를 해결했습니다 .

CABasicAnimation* ba = [CABasicAnimation animationWithKeyPath:@"transform"];
ba.duration = 0.38f;
ba.fillMode = kCAFillModeForwards;
ba.removedOnCompletion = NO;
ba.autoreverses = NO;
ba.repeatCount = 0;
ba.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.85f, 0.85f, 1.0f)];
[myView.layer addAnimation:ba forKey:nil];

이 코드 myView는 크기의 85 % (3 차원은 변경되지 않음)로 변환됩니다.


사용하지 않고 removedOnCompletion

이 기술을 시도해 볼 수 있습니다.

self.animateOnX(item: shapeLayer)

func animateOnX(item:CAShapeLayer)
{
    let endPostion = CGPoint(x: 200, y: 0)
    let pathAnimation = CABasicAnimation(keyPath: "position")
    //
    pathAnimation.duration = 20
    pathAnimation.fromValue = CGPoint(x: 0, y: 0)//comment this line and notice the difference
    pathAnimation.toValue =  endPostion
    pathAnimation.fillMode = kCAFillModeBoth

    item.position = endPostion//prevent the CABasicAnimation from resetting item's position when the animation finishes

    item.add(pathAnimation, forKey: nil)
}

@Leslie Godwin's answer is not really good, "self.view.layer.opacity = 1;" is done immediately (it takes about one second), please fix alphaAnimation.duration to 10.0, if you have doubts. You have to remove this line.

So, when you fix fillMode to kCAFillModeForwards and removedOnCompletion to NO, you let the animation remains in the layer. If you fix the animation delegate and try something like:

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
 [theLayer removeAllAnimations];
}

...the layer restores immediately at the moment you execute this line. It's what we wanted to avoid.

You must fix the layer property before remove the animation from it. Try this:

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
     if([anim isKindOfClass:[CABasicAnimation class] ]) // check, because of the cast
    {
        CALayer *theLayer = 0;
        if(anim==[_b1 animationForKey:@"opacity"])
            theLayer = _b1; // I have two layers
        else
        if(anim==[_b2 animationForKey:@"opacity"])
            theLayer = _b2;

        if(theLayer)
        {
            CGFloat toValue = [((CABasicAnimation*)anim).toValue floatValue];
            [theLayer setOpacity:toValue];

            [theLayer removeAllAnimations];
        }
    }
}

This works:

let animation = CABasicAnimation(keyPath: "opacity")
animation.fromValue = 0
animation.toValue = 1
animation.duration = 0.3

someLayer.opacity = 1 // important, this is the state you want visible after the animation finishes.
someLayer.addAnimation(animation, forKey: "myAnimation")

Core animation shows a 'presentation layer' atop your normal layer during the animation. So set the opacity (or whatever) to what you want to be seen when the animation finishes and the presentation layer goes away. Do this on the line before you add the animation to avoid a flicker when it completes.

If you want to have a delay, do the following:

let animation = CABasicAnimation(keyPath: "opacity")
animation.fromValue = 0
animation.toValue = 1
animation.duration = 0.3
animation.beginTime = someLayer.convertTime(CACurrentMediaTime(), fromLayer: nil) + 1
animation.fillMode = kCAFillModeBackwards // So the opacity is 0 while the animation waits to start.

someLayer.opacity = 1 // <- important, this is the state you want visible after the animation finishes.
someLayer.addAnimation(animation, forKey: "myAnimation")

Finally, if you use 'removedOnCompletion = false' it'll leak CAAnimations until the layer is eventually disposed - avoid.


So my problem was that I was trying to rotate an object on pan gesture and so I had multiple identical animations on each move. I had both fillMode = kCAFillModeForwards and isRemovedOnCompletion = false but it didn't help. In my case, I had to make sure that the animation key is different each time I add a new animation:

let angle = // here is my computed angle
let rotate = CABasicAnimation(keyPath: "transform.rotation.z")
rotate.toValue = angle
rotate.duration = 0.1
rotate.isRemovedOnCompletion = false
rotate.fillMode = kCAFillModeForwards

head.layer.add(rotate, forKey: "rotate\(angle)")

Core animation maintains two layer hierarchies: the model layer and the presentation layer. When the animation is in progress, the model layer is actually intact and keeps it initial value. By default, the animation is removed once the it's completed. Then the presentation layer falls back to the value of the model layer.

Simply setting removedOnCompletion to NO means the animation won't be removed and wastes memory. In addition, the model layer and the presentation layer won't be synchronous any more, which may lead to potential bugs.

So it would be a better solution to update the property directly on the model layer to the final value.

self.view.layer.opacity = 1;
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
animation.fromValue = 0;
animation.toValue = 1;
[self.view.layer addAnimation:animation forKey:nil];

If there's any implicit animation caused by the first line of above code, try to turn if off:

[CATransaction begin];
[CATransaction setDisableActions:YES];
self.view.layer.opacity = 1;
[CATransaction commit];

CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
animation.fromValue = 0;
animation.toValue = 1;
[self.view.layer addAnimation:animation forKey:nil];

It seems that removedOnCompletion flag set to false and fillMode set to kCAFillModeForwards doesn't work for me either.

After I apply new animation on a layer, an animating object resets to its initial state and then animates from that state. What has to be done additionally is to set the model layer's desired property according to its presentation layer's property before setting new animation like so:

someLayer.path = ((CAShapeLayer *)[someLayer presentationLayer]).path;
[someLayer addAnimation:someAnimation forKey:@"someAnimation"];

The easiest solution is to use implicit animations. This will handle all of that trouble for you:

self.layer?.backgroundColor = NSColor.red.cgColor;

If you want to customize e.g. the duration, you can use NSAnimationContext:

    NSAnimationContext.beginGrouping();
    NSAnimationContext.current.duration = 0.5;
    self.layer?.backgroundColor = NSColor.red.cgColor;
    NSAnimationContext.endGrouping();

Note: This is only tested on macOS.

I initially did not see any animation when doing this. The problem is that the layer of a view-backed layer does not implicit animate. To solve this, make sure you add a layer yourself (before setting the view to layer-backed).

An example how to do this would be:

override func awakeFromNib() {
    self.layer = CALayer();
    //self.wantsLayer = true;
}

Using self.wantsLayer did not make any difference in my testing, but it could have some side effects that I do not know of.


Here is a sample from playground:

import PlaygroundSupport
import UIKit

let resultRotation = CGFloat.pi / 2
let view = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 200.0, height: 300.0))
view.backgroundColor = .red

//--------------------------------------------------------------------------------

let rotate = CABasicAnimation(keyPath: "transform.rotation.z") // 1
rotate.fromValue = CGFloat.pi / 3 // 2
rotate.toValue = resultRotation // 3
rotate.duration = 5.0 // 4
rotate.beginTime = CACurrentMediaTime() + 1.0 // 5
// rotate.isRemovedOnCompletion = false // 6
rotate.fillMode = .backwards // 7

view.layer.add(rotate, forKey: nil) // 8
view.layer.setAffineTransform(CGAffineTransform(rotationAngle: resultRotation)) // 9

//--------------------------------------------------------------------------------

PlaygroundPage.current.liveView = view
  1. Create an animation model
  2. Set start position of the animation (could be skipped, it depends on your current layer layout)
  3. Set end position of the animation
  4. Set animation duration
  5. Delay animation for a second
  6. Do not set false to isRemovedOnCompletion - let Core Animation clean after the animation is finished
  7. Here is the first part of the trick - you say to Core Animation to place your animation to the start position (you set in step #2) before the animation has even been started - extend it backwards in time
  8. Copy prepared animation object, add it to the layer and start the animation after the delay (you set in step #5)
  9. The second part is to set correct end position of the layer - after the animation is deleted your layer will be shown at the correct place

참고URL : https://stackoverflow.com/questions/6059054/cabasicanimation-resets-to-initial-value-after-animation-completes

반응형