How to animate the frame of an layer with CABasicAnimation?

℡╲_俬逩灬. 提交于 2019-12-17 15:44:37

问题


I guess I have to convert the CGRect into an object to pass it to fromValue?

This is how I try it, but it doesn't work:

CABasicAnimation *frameAnimation = [CABasicAnimation animationWithKeyPath:@"frame"];
frameAnimation.duration = 2.5;
frameAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
frameAnimation.fromValue = [NSValue valueWithCGRect:myLayer.frame];
frameAnimation.toValue = [NSValue valueWithCGRect:theNewFrameRect];
[myLayer addAnimation:frameAnimation forKey:@"MLC"];

回答1:


I guess you need to change your last line to make it work:

[myLayer addAnimation:frameAnimation forKey:@"frame"];

You may also set an action to the layer to make all frame changes animated with your animation:

CABasicAnimation *frameAnimation = [CABasicAnimation animation];
frameAnimation.duration = 2.5;
frameAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];

myLayer.actions = [NSDictionary dictionaryWithObjectsAndKeys:frameAnimation, @"frame", nil];

In CALayer's actionForKey: method reference you can find how layer looks up for the actions to animated its properties.




回答2:


The frame property of a CALayer is a derived property, dependent on the position, anchorPoint, bounds and transform of the layer. Instead of animating the frame, you should instead animate the position or bounds, depending on what effect you are trying to accomplish.

To move a layer, you can animate the position:

-(void)moveLayer:(CALayer*)layer to:(CGPoint)point
{
    // Prepare the animation from the current position to the new position
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
    animation.fromValue = [layer valueForKey:@"position"];

    // NSValue/+valueWithPoint:(NSPoint)point is available on Mac OS X
    // NSValue/+valueWithCGPoint:(CGPoint)point is available on iOS
    // comment/uncomment the corresponding lines depending on which platform you're targeting

    // Mac OS X
    animation.toValue = [NSValue valueWithPoint:NSPointFromCGPoint(point)];
    // iOS
    //animation.toValue = [NSValue valueWithCGPoint:point];

    // Update the layer's position so that the layer doesn't snap back when the animation completes.
    layer.position = point;

    // Add the animation, overriding the implicit animation.
    [layer addAnimation:animation forKey:@"position"];
}

To resize a layer, you would animate the bounds parameter:

-(void)resizeLayer:(CALayer*)layer to:(CGSize)size
{
    // Prepare the animation from the old size to the new size
    CGRect oldBounds = layer.bounds;
    CGRect newBounds = oldBounds;
    newBounds.size = size;
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"bounds"];

    // NSValue/+valueWithRect:(NSRect)rect is available on Mac OS X
    // NSValue/+valueWithCGRect:(CGRect)rect is available on iOS
    // comment/uncomment the corresponding lines depending on which platform you're targeting

    // Mac OS X
    animation.fromValue = [NSValue valueWithRect:NSRectFromCGRect(oldBounds)];
    animation.toValue = [NSValue valueWithRect:NSRectFromCGRect(newBounds)];
    // iOS
    //animation.fromValue = [NSValue valueWithCGRect:oldBounds];
    //animation.toValue = [NSValue valueWithCGRect:newBounds];

    // Update the layer's bounds so the layer doesn't snap back when the animation completes.
    layer.bounds = newBounds;

    // Add the animation, overriding the implicit animation.
    [layer addAnimation:animation forKey:@"bounds"];
}

You can combine these animations using a CAAnimationGroup if you need to move and resize a layer at the same time.




回答3:


we can change the properties of "bounds" and "position" to animate it, such as

-(void)handleTap2:(UITapGestureRecognizer *)recognizer {

    UIImageView *vw = (UIImageView *)[recognizer view];

    CGPoint startPoint = CGPointMake(vw.frame.size.width/2+vw.frame.origin.x, vw.frame.size.height/2+vw.frame.origin.y); 
    CGPoint endPoint = CGPointMake(160, 240);

    CGRect startBounds = vw.bounds; 
    CGRect stopBounds = self.view.bounds;

    layer = [CALayer layer]; 
    layer.frame = self.view.frame; 
    layer.contents = (id)[vw.image CGImage];

    [self.view.window.layer addSublayer:layer];

    CABasicAnimation * baseAnimation = [CABasicAnimation animationWithKeyPath:@"position"];

    baseAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; 
    baseAnimation.fromValue = [NSValue valueWithCGPoint:startPoint] ; 
    baseAnimation.toValue = [NSValue valueWithCGPoint:endPoint] ;

    CABasicAnimation * boundsAnimation = [CABasicAnimation animationWithKeyPath:@"bounds"];

    boundsAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; 
    boundsAnimation.fromValue = [NSValue valueWithCGRect:startBounds] ; boundsAnimation.toValue = [NSValue valueWithCGRect:stopBounds] ;

    CAAnimationGroup * group =[CAAnimationGroup animation]; 
    group.removedOnCompletion=NO; group.fillMode=kCAFillModeForwards; 
    group.animations =[NSArray arrayWithObjects:baseAnimation, boundsAnimation, nil];    
    group.duration = 0.7;

    [layer addAnimation:group forKey:@"frame"]; 
}



回答4:


The question is antique, but I will answer it anyway.

Frame property is not animatable. You have to animate other properties. Also you have to disable implicit animations.

    let updatedBounds = ...
    let animation = CABasicAnimation(keyPath: "bounds")
    animation.duration = 0.5
    //it's better to start animation from presentation layer in case there is already animation going on
    animation.fromValue = customLayer.presentation()?.bounds
    animation.toValue = updatedBounds
    customLayer.add(animation, forKey: nil)

    //disable implicit animation for thoose properties
    CATransaction.begin()
    CATransaction.setDisableActions(true)
    //update properties so they will be updated at the end of animation
    customLayer.bounds = updatedBounds
    customLayer.position = originalRect.origin
    customLayer.anchorPoint = CGPoint(x: 0, y: 0)
    CATransaction.commit()



回答5:


Here's a simple, fully working, example which may help someone.

Just call .slideUp() on the class and it will slide up.

class Slidey: YourViewClass {

    func slideUp() {

        print("\n\n SLIDE")

        let FF = layer.position
        var TT = FF
        TT.y -= 100
        print(FF)
        print(TT)

        CATransaction.begin()
        CATransaction.setDisableActions(true)

        CATransaction.setCompletionBlock{ [weak self] in

            print("DONE")
        }

        let a = CABasicAnimation(keyPath: "position")

        a.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)

        a.isCumulative = false
        a.autoreverses = false
        a.isRemovedOnCompletion = true
        a.repeatCount = 0
        a.fromValue = FF
        a.toValue = TT
        a.duration = 0.70
        layer.add(a, forKey: nil)

        CATransaction.commit()
    }
}



回答6:


Extension in swift 4

import UIKit

extension CALayer {
    func moveTo(point: CGPoint, animated: Bool) {
        if animated {
            let animation = CABasicAnimation(keyPath: "position")
            animation.fromValue = value(forKey: "position")
            animation.toValue = NSValue(cgPoint: point)
            animation.fillMode = .forwards
            self.position = point
            add(animation, forKey: "position")
        } else {
            self.position = point
        }
    }

    func resize(to size: CGSize, animated: Bool) {
        let oldBounds = bounds
        var newBounds = oldBounds
        newBounds.size = size

        if animated {
            let animation = CABasicAnimation(keyPath: "bounds")
            animation.fromValue = NSValue(cgRect: oldBounds)
            animation.toValue = NSValue(cgRect: newBounds)
            animation.fillMode = .forwards
            self.bounds = newBounds
            add(animation, forKey: "bounds")
        } else {
            self.bounds = newBounds
        }
    }

    func resizeAndMove(frame: CGRect, animated: Bool, duration: TimeInterval = 0) {
        if animated {
            let positionAnimation = CABasicAnimation(keyPath: "position")
            positionAnimation.fromValue = value(forKey: "position")
            positionAnimation.toValue = NSValue(cgPoint: CGPoint(x: frame.midX, y: frame.midY))

            let oldBounds = bounds
            var newBounds = oldBounds
            newBounds.size = frame.size

            let boundsAnimation = CABasicAnimation(keyPath: "bounds")
            boundsAnimation.fromValue = NSValue(cgRect: oldBounds)
            boundsAnimation.toValue = NSValue(cgRect: newBounds)

            let groupAnimation = CAAnimationGroup()
            groupAnimation.animations = [positionAnimation, boundsAnimation]
            groupAnimation.fillMode = .forwards
            groupAnimation.duration = duration
            groupAnimation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
            self.frame = frame
            add(groupAnimation, forKey: "frame")

        } else {
            self.frame = frame
        }
    }
}


来源:https://stackoverflow.com/questions/2892888/how-to-animate-the-frame-of-an-layer-with-cabasicanimation

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!