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


143

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

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

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

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


1
이것은 거의 모든 답이 완전히 틀린 이상한 SO 질문 중 하나입니다 . 당신은 단순히 .presentation()"최종, 본"값을 얻기 위해 본다 . 아래에서 정답 레이어를 찾아서 프리젠 테이션 레이어로 완료된 것을 설명하십시오.
Fattie

답변:


285

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

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

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


6
이것은 정답이 아닐 수도 있습니다. Krishnan의 답변에 대한 의견을 참조하십시오. 당신은 보통 이것과 크리슈 난이 필요합니다. 하나 또는 다른 하나만 작동하지 않습니다.
Adam

19
정답이 아닙니다. 아래 @Leslie Godwin의 답변을 참조하십시오.
Mark Ingram

6
@Leslie 솔루션은 애니메이션이 아닌 레이어에 최종 값을 저장하기 때문에 더 좋습니다.
Matthieu Rouif

1
removedOnCompletion을 NO로 설정할 때주의하십시오. 애니메이션 델리게이트를 "self"에 할당하면 애니메이션이 인스턴스를 유지합니다. 따라서 removeFromSuperview를 호출 한 후 직접 애니메이션을 제거 / 파괴하는 것을 잊어 버린 경우, 예를 들어 dealloc이 호출되지 않습니다. 메모리 누수가 발생합니다.
emrahgunduz

1
이 200 + 포인트 답변은 완전히, 완전히, 완전히 잘못되었습니다.
Fattie

83

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

7
시작 지연을 설정하면 작동하지 않습니다. 예, alphaAnimation.beginTime = 1; 또한 fillMode내가 말할 수있는 한 필요하지 않습니다 ...?
Sam

그렇지 않으면, 이것은 좋은 답변입니다. 허용 된 답변을 통해 애니메이션이 완료된 후 값을 변경할 수 없습니다.
Sam

2
나는 또한주의해야 beginTime상대하지, 당신은 사용해야 예 :CACurrentMediaTime()+1;

'그것'이 아니라 '그것'이 아닙니다-its-not-its.info. 오타라면 죄송합니다.
fatuhoku


16

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

animationObject.removedOnCompletion = NO;

@Nilesh : 답변을 수락하십시오. 다른 SO 사용자에게 귀하의 답변에 대한 자신감을 줄 것입니다.
Krishnan

7
.fillMode = kCAFillModeForwards와 .removedOnCompletion = NO를 모두 사용해야했습니다. 감사!
William Rust

12

그냥 코드 안에 넣으십시오

CAAnimationGroup *theGroup = [CAAnimationGroup animation];

theGroup.fillMode = kCAFillModeForwards;

theGroup.removedOnCompletion = NO;

12

당신은 단순히의 주요 설정할 수 있습니다 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 (비디오 중간)에 대한 자세한 정보를 얻을 수 있습니다.


1
이것은 올바른 방법입니다. 애니메이션은 자연스럽게 제거되고 (기본값) 애니메이션이 완료되면 애니메이션이 시작되기 전에 설정된 값, 특히이 줄로 돌아갑니다 someLayer.position = endPosition;. 그리고 WWDC에 대한 심판에 감사드립니다!
bauerMusic

10

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

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

self.view.layer.opacity = 1;

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

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

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


8

사용하지 않고 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)
}

3
이것은 최선의 기능의 해답이 될 것 같다
rambossa

동의했다. @ayman 의 의견에 따르면 .fromValue 속성을 시작 값으로 정의 해야합니다 . 그렇지 않으면 모델의 시작 값을 덮어 쓰게되며 애니메이션이 나타납니다.
Jeff Collier

이 답변과 @ jason-moore의 답변이 허용되는 답변보다 낫습니다. 수락 된 답변에서 애니메이션은 일시 중지되었지만 새 값은 실제로 속성으로 설정되지 않았습니다. 원치 않는 동작이 발생할 수 있습니다.
laka

8

그래서 내 문제는 팬 제스처에서 객체를 회전 시키려고했기 때문에 각 움직임마다 동일한 애니메이션이 여러 개 있다는 것입니다. 나는 모두를했다 fillMode = kCAFillModeForwards하고 isRemovedOnCompletion = false있지만 도움이되지 않았다. 필자의 경우 새 애니메이션을 추가 할 때마다 애니메이션 키가 달라야합니다 .

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 = CAMediaTimingFillMode.forwards

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

7

단순히 설정 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 차원은 변경되지 않음)로 변환됩니다.


7

핵심 애니메이션은 두 가지 계층 계층, 즉 모델 계층프레젠테이션 계층을 유지 관리 합니다. 애니메이션이 진행중인 경우 모델 레이어는 그대로 유지되며 초기 값을 유지합니다. 기본적으로 애니메이션은 완료되면 제거됩니다. 그런 다음 프리젠 테이션 레이어는 모델 레이어의 값으로 돌아갑니다.

간단히 설정 removedOnCompletionNO애니메이션이 제거되지 않습니다 수단 및 폐기물 메모리. 또한 모델 레이어와 프리젠 테이션 레이어는 더 이상 동기화되지 않으므로 잠재적 인 버그가 발생할 수 있습니다.

따라서 모델 레이어의 속성을 최종 값으로 직접 업데이트하는 것이 더 나은 솔루션입니다.

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

위 코드의 첫 번째 줄로 인해 내재 된 애니메이션이있는 경우 끄십시오.

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

감사합니다! 제대로 작동하도록 단지 반창고를 사용하는 대신이 기능을 설명 이것은 정말, 허용 대답해야한다
bplattenburg

6

이것은 작동합니다 :

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")

핵심 애니메이션은 애니메이션 중 일반 레이어 위에 '프레젠테이션 레이어'를 보여줍니다. 따라서 애니메이션이 끝나고 프리젠 테이션 레이어가 사라지면 불투명도 (또는 무엇이든)를 원하는 것으로 설정합니다. 애니메이션을 추가 하기 전에 줄에서이 작업을 수행하면 완료 될 때 깜박임을 피할 수 있습니다.

지연을 원하면 다음을 수행하십시오.

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")

마지막으로 'removedOnCompletion = false'를 사용하면 레이어가 결국 폐기 될 때까지 CAAnimations가 누출됩니다. 피하십시오.


훌륭한 답변, 유용한 팁. removedOnCompletion 댓글에 감사드립니다.
iWheel1

4

@Leslie Godwin의 답변은 "self.view.layer.opacity = 1;" 즉시 완료됩니다 (약 1 초 정도 소요). 의심스러운 경우 alphaAnimation.duration을 10.0으로 수정하십시오. 이 줄을 제거해야합니다.

따라서 fillMode를 kCAFillModeForwards로, removedOnCompletion을 NO로 고정하면 애니메이션이 레이어에 남아있게됩니다. 애니메이션 대리자를 수정하고 다음과 같이 시도하면

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

...이 라인을 실행하는 순간 레이어가 즉시 복원됩니다. 우리가 피하고 싶었던 것입니다.

애니메이션을 제거하기 전에 레이어 속성을 수정해야합니다. 이 시도:

- (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];
        }
    }
}

1

removedOnCompletion 플래그가 false로 설정되고 fillMode가 kCAFillModeForwards로 설정되어있는 것 같습니다.

레이어에 새 애니메이션을 적용하면 애니메이션 객체가 초기 상태로 재설정 된 다음 해당 상태에서 애니메이션이 적용됩니다. 추가로해야 할 일은 새로운 애니메이션을 설정하기 전에 프리젠 테이션 레이어의 속성에 따라 모델 레이어의 원하는 속성을 설정하는 것입니다.

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

0

가장 쉬운 해결책은 암시 적 애니메이션을 사용하는 것입니다. 이것은 당신을 위해 그 모든 문제를 다룰 것입니다 :

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

지속 시간 등을 사용자 정의하려면 다음을 사용할 수 있습니다 NSAnimationContext.

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

참고 : 이것은 macOS에서만 테스트되었습니다.

처음에는이 작업을 수행 할 때 애니메이션이 표시되지 않았습니다. 문제는 뷰 백 레이어의 레이어가 내재적 애니메이션을 하지 않는다는 것 입니다. 이 문제를 해결하려면보기를 레이어 지원으로 설정하기 전에 레이어를 직접 추가해야합니다.

이를 수행하는 방법의 예는 다음과 같습니다.

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

사용하면 self.wantsLayer테스트에서 아무런 차이가 없었지만 모르는 부작용이있을 수 있습니다.


0

놀이터의 샘플은 다음과 같습니다.

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. 애니메이션 모델 만들기
  2. 애니메이션의 시작 위치 설정 합니다 (건너 뛸 수 있으며 현재 레이어 레이아웃에 따라 다름)
  3. 애니메이션의 끝 위치 설정
  4. 애니메이션 지속 시간 설정
  5. 잠시 애니메이션 지연
  6. 설정하지 마십시오 falseisRemovedOnCompletion- 애니메이션이 완료된 후 코어 애니메이션 청소하자
  7. 다음은 트릭의 첫 번째 부분입니다. 애니메이션이 시작되기 전에 애니메이션을 시작 위치 (2 단계에서 설정)에 배치하도록 Core Animation에 말하십시오.
  8. 준비된 애니메이션 객체를 복사하여 레이어에 추가하고 지연 후 애니메이션을 시작합니다 (5 단계에서 설정)
  9. 두 번째 부분레이어의 올바른 끝 위치를 설정 하는 것입니다. 애니메이션이 삭제 된 후 레이어가 올바른 위치에 표시됩니다
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.