I'm trying to do an additive animation on a AVPlayerLayer's bounds. By simply setting additive to YES, it breaks the animation. I need to do it additive so that the width and height can be animated with different animation parameters. Animating the key paths bounds.size.width and bounds.size.height do not work either.
I can animate other CALayer classes fine with the below code, so I'm starting to wonder if AVPlayerLayer has a bug.
Here is the code:
AVPlayerLayer *vl;
// ...
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
NSURL *path = [[NSBundle mainBundle] URLForResource:#"vid" withExtension:#"mp4"]];
AVPlayer *player = [AVPlayer playerWithURL: path];
vl = [AVPlayerLayer playerLayerWithPlayer:player];
vl.videoGravity = AVLayerVideoGravityResize;
// vl = [CALayer layer]; // a normal calayer animates properly
vl.backgroundColor = [NSColor redColor].CGColor;
vl.frame = CGRectMake(200,100,150,100);
[_window.contentView.layer addSublayer:vl];
}
- (IBAction)animate:(id)sender {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"bounds"];
animation.fromValue = [NSValue valueWithRect:CGRectMake(0,0,0, 0)];
animation.toValue = [NSValue valueWithRect:CGRectMake(0,0,240, 60)];
animation.duration = 4;
animation.additive = YES;
[vl addAnimation:animation forKey:nil];
}
This is what it looks like:
The red area should be the video playing.
You can check out the sample project I created here:
https://www.dropbox.com/s/jxuh69wiqdwnuc6/PlayerLayer%20size%20Animation.zip?dl=1
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.
You may first take a look at this:
Q: When I try to animate the frame of a CALayer nothing happens. Why?
This can also help:
How to animate the frame of an layer with CABasicAnimation?
And those code may be helpul to you:
NSURL *fileUrl = [NSURL fileURLWithPath:#"yourFilePath"];
AVPlayer *player = [[AVPlayer alloc] initWithURL:fileUrl];
CALayer *superlayer = self.view.layer;
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];
[superlayer addSublayer:playerLayer];
[player play];
playerLayer.videoGravity = AVLayerVideoGravityResize;
playerLayer.frame = CGRectMake(10, 200, 320, 200);
playerLayer.position = CGPointMake(self.view.bounds.size.width/2, self.view.bounds.size.height/2);
CGRect startBounds = CGRectMake(10, 200, 200, 150);
CGRect endBounds = CGRectMake(10, 200, 320, 200);
CABasicAnimation * sizeAnimation = [CABasicAnimation animationWithKeyPath:#"bounds"];
sizeAnimation.fromValue = [NSValue valueWithRect:startBounds] ;
sizeAnimation.toValue = [NSValue valueWithRect:endBounds] ;
sizeAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
sizeAnimation.duration = 4;
[playerLayer addAnimation:sizeAnimation forKey:#"bounds"];
Related
I have a very basic test scene set up: one camera, default lighting and one slowly rotating cube.
I am attempting to add a video as the texture of the cube and have been following the example here to do so.
Here is the specific block of code that is supposed to add an AVPlayerLayer as the material for a node's geometry:
NSURL *url = [[NSBundle mainBundle] URLForResource:#"testVid" withExtension:#"mp4"];
AVAsset *asset = [AVAsset assetWithURL:url];
if (!asset.playable) {
NSLog(#"Err 1");
return;
}
AVPlayerItem *item = [[AVPlayerItem alloc] initWithAsset:asset];
AVPlayerLayer *layer = [[AVPlayerLayer alloc] init];
layer.player = [AVPlayer playerWithPlayerItem:item];
layer.player.actionAtItemEnd = AVPlayerActionAtItemEndNone;
[layer.player play];
layer.videoGravity = AVLayerVideoGravityResizeAspectFill;
layer.frame = CGRectMake(0, 0, 600, 800);
CALayer *backLayer = [CALayer layer];
backLayer.backgroundColor = [[UIColor blackColor] CGColor];
backLayer.frame = CGRectMake(0, 0, 600, 800);
[backLayer addSublayer:layer];
self.cubeNode.geometry.firstMaterial.diffuse.contents = backLayer;
//[self.sceneView.layer addSublayer:backLayer];
If I add the layer to a UIView (see commented out line) then I get nice playback fine. However, if I add it to the node I just get a black spinning cube (i.e. the backLayer shows up but not the video).
Is there something I'm missing? Or is this actually not possible?
I would like to synchronize the movement of an SKSpriteNode with a video. So as the video seeks forward, seeks backward or just plays the SKSpriteNode moves accordingly.
I know this can be done easily enough with CALayers, but can it be done with SKSpriteNode(s) in place of CALayers?
// 1) Create a sync layer
AVPlayerItem * item = player.currentItem;
AVSynchronizedLayer* syncLayer = [AVSynchronizedLayer synchronizedLayerWithPlayerItem:item];
syncLayer.frame = CGRectMake(10, 220, 300, 10);
syncLayer.backgroundColor = [[UIColor lightGrayColor] CGColor];
[self.view.layer addSublayer:syncLayer];
// 2) Create a sublayer for the synclayer
CALayer *sublayer = [CALayer layer];
sublayer.frame = CGRectMake(0, 0, 200, 50); //our black box
sublayer.backgroundColor = [[UIColor blackColor] CGColor];
[syncLayer addSublayer:sublayer];
// 3) animate the layer!
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:#"position"];
anim.fromValue = [NSValue valueWithCGPoint:CGPointMake(924, 300)];
anim.toValue = [NSValue valueWithCGPoint:CGPointMake(100, 300)];
anim.removedOnCompletion = NO;
anim.beginTime = AVCoreAnimationBeginTimeAtZero;
anim.duration = CMTimeGetSeconds(item.asset.duration);
NSLog(#"%f",anim.duration);
[sublayer addAnimation:anim forKey:nil];
I am making an application where I have to show 3 images in a single screen, and when User touch one image then that Image should be shown by Animating and resizing.
So for 1st Step I did Masking of 3 images with 3 different Transparent Mask Image from
How to Mask an UIImageView
And my method is like below
- (UIImageView*) maskedImage:(UIImage *)image withMasked:(UIImage *)maskImage {
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
CALayer *mask1 = [CALayer layer];
mask1.contents = (id)[maskImage CGImage];
mask1.frame = CGRectMake(0, 0, 1024, 768);
imageView.layer.mask = mask1;
imageView.layer.masksToBounds = YES;
return imageView;
}
And I am calling this as
self.image2 = [UIImage imageNamed:#"screen2Full.png"];
self.mask2 = [UIImage imageNamed:#"maskImage.png"];
self.imageView2 = [self maskedImage:self.image2 withMasked:self.mask2];
[self.completeSingleView addSubview:self.imageView2];
This is working perfectly.
Now for Step 2. Animate the Mask, so that Full Image can be shown. I have googled a lot about this but didn't get success. So Please help me. I just want to know how can we Animate and Resize the Mask Image So that I can view a full Image. My concept is as below.
So finally i got the solution from multiple sites and some own logic and hard work obviously ;). So here is the solution of this
-(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"];
animation.toValue = [NSValue valueWithCGPoint:point];
animation.duration = .3;
layer.position = point;
[layer addAnimation:animation forKey:#"position"];
}
-(void)resizeLayer:(CALayer*)layer to:(CGSize)size
{
CGRect oldBounds = layer.bounds;
CGRect newBounds = oldBounds;
newBounds.size = size;
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"bounds"];
animation.fromValue = [NSValue valueWithCGRect:oldBounds];
animation.toValue = [NSValue valueWithCGRect:newBounds];
animation.duration = .3;
layer.bounds = newBounds;
[layer addAnimation:animation forKey:#"bounds"];
}
- (UIImageView*) animateMaskImage:(UIImage *)image withMask:(UIImage *)maskImage width:(int )wd andHeight:(int )ht andSize:(CGSize)size andPosition:(CGPoint)pt {
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
CALayer *mask = [CALayer layer];
mask.contents = (id)[maskImage CGImage];
mask.frame = CGRectMake(0, 0, wd, ht);
//This needs to be place before masking. I don't knwo why. But its working :)
[self resizeLayer:mask to:size];
[self moveLayer:mask to:pt];
imageView.layer.mask = mask;
imageView.layer.masksToBounds = YES;
return imageView;
}
You just need to call this as
[self.imageView1 removeFromSuperview];
self.imageView1 = [self animateMaskedImage:self.image1 withMasked:self.mask1 width:1024 andHeight:768 andSize:CGSizeMake(1024*4, 768*4) andPosition:CGPointMake(1024*2, 768*2)];
[self.completeSingleView addSubview:self.imageView1];
I've been unsuccessful at animating a flashing stroke on a CAShapeLayer using the answer from this previous thread, and after many searches I can find no other examples of animating the stroke using CABasicAnimation.
What I want to do is have the stroke of my CAShapeLayer pulse between two colors. Using CABasicAnimation for opacity works fine, but the [CABasicAnimation animationWithKeyPath:#"strokeColor"] eludes me, and I'd appreciate any advice on how to successfully implement.
CABasicAnimation *strokeAnim = [CABasicAnimation animationWithKeyPath:#"strokeColor"];
strokeAnim.fromValue = (id) [UIColor blackColor].CGColor;
strokeAnim.toValue = (id) [UIColor purpleColor].CGColor;
strokeAnim.duration = 1.0;
strokeAnim.repeatCount = 0.0;
strokeAnim.autoreverses = NO;
[shapeLayer addAnimation:strokeAnim forKey:#"animateStrokeColor"];
// CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:#"opacity"];
// opacityAnimation.fromValue = [NSNumber numberWithFloat:0.0];
// opacityAnimation.toValue = [NSNumber numberWithFloat:1.0];
// opacityAnimation.duration = 1.0;
// opacityAnimation.repeatCount = 0.0;
// opacityAnimation.autoreverses = NO;
// [shapeLayer addAnimation:opacityAnimation forKey:#"animateOpacity"];
Uncommenting the opacity animation results in an expected opacity fade. The stroke animation produces no effect. An implicit strokeColor change animates as expected, but I would like documented confirmation that strokeColor can be explicitly animated using CABasicAnimation.
Update: The specific problem was that shapeLayer.path was NULL. Correcting that fixed the problem.
The code below works great for me. What is the lineWidth of your shapeLayer stroke path? Could that be the issue?
- (void)viewDidLoad
{
[super viewDidLoad];
UIBezierPath * circle = [UIBezierPath bezierPathWithOvalInRect:self.view.bounds];
CAShapeLayer * shapeLayer = [CAShapeLayer layer];
shapeLayer.path = circle.CGPath;
shapeLayer.strokeColor =[UIColor blueColor].CGColor;
[shapeLayer setLineWidth:15.0];
[self.view.layer addSublayer:shapeLayer];
CABasicAnimation *strokeAnim = [CABasicAnimation animationWithKeyPath:#"strokeColor"];
strokeAnim.fromValue = (id) [UIColor redColor].CGColor;
strokeAnim.toValue = (id) [UIColor greenColor].CGColor;
strokeAnim.duration = 3.0;
strokeAnim.repeatCount = 0;
strokeAnim.autoreverses = YES;
[shapeLayer addAnimation:strokeAnim forKey:#"animateStrokeColor"];
}
Let me know if it works for you...
Currently, I figured out how to animate an image that is in my UIViewController:
UIImage *image = [UIImage imageNamed:#"Logo.png"];
CALayer *logoLayer = [CALayer layer];
logoLayer.bounds = CGRectMake(0, 0, image.size.width, image.size.height);
logoLayer.position = CGPointMake(300, 216);
logoLayer.contents = (id)image.CGImage;
CAKeyframeAnimation *moveAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
moveAnimation.path = path.CGPath;
moveAnimation.duration = 2.0f;
[logoLayer addAnimation:moveAnimation forKey:#"moveAnimation"];
CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
animationGroup.duration = 2.0f;
animationGroup.autoreverses = NO;
animationGroup.repeatCount = 1; //HUGE_VALF
[animationGroup setAnimations:[NSArray arrayWithObjects: moveAnimation, nil]];
[logoLayer addAnimation:animationGroup forKey:#"animationGroup"];
But this is based on a logoLayer, which is an image. How could I animate something like this but for a UIView? Such as a UIButton?
In general, rather than using CAAnimations on CALayers, you can use UIView animation. There are ways to set animation curve, delay, repeat, reversal, and so on using the block-based animation API or the old delegate-based API.
As for your specific case of the CAKeyframeAnimation, you can try the answer to this question.