CABasicAnimation resets to initial value after animation completes

后端 未结 15 2470
闹比i
闹比i 2020-12-04 05:44

I am rotating a CALayer and trying to stop it at its final position after animation is completed.

But after animation completes it resets to its initial position.

相关标签:
15条回答
  • 2020-12-04 05:56

    Without using the removedOnCompletion

    You can try this technique:

    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)
    }
    
    0 讨论(0)
  • 2020-12-04 05:56

    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 = CAMediaTimingFillMode.forwards
    
    head.layer.add(rotate, forKey: "rotate\(angle)")
    
    0 讨论(0)
  • 2020-12-04 05:56

    @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];
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-04 06:05

    A CALayer has a model layer and a presentation layer. During an animation, the presentation layer updates independently of the model. When the animation is complete, the presentation layer is updated with the value from the model. If you want to avoid a jarring jump after the animation ends, the key is to keep the two layers in sync.

    If you know the end value, you can just set the model directly.

    self.view.layer.opacity = 1;
    

    But if you have an animation where you don't know the end position (e.g. a slow fade that the user can pause and then reverse), then you can query the presentation layer directly to find the current value, and then update the model.

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

    Pulling the value from the presentation layer is also particularly useful for scaling or rotation keypaths. (e.g. transform.scale, transform.rotation)

    0 讨论(0)
  • 2020-12-04 06:05

    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
    0 讨论(0)
  • 2020-12-04 06:06

    You can simply set the key of CABasicAnimation to position when you add it to the layer. By doing this, it will override implicit animation done on the position for the current pass in the run loop.

    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
    

    You can have more information on 2011 Apple WWDC Video Session 421 - Core Animation Essentials (middle of the video)

    0 讨论(0)
提交回复
热议问题