这篇随笔主要介绍有关图层显式动画。
• 显示动画: 
   
• 能对一些属性做指定的自定义动画,或者创建非线性动画
• 属性动画: 
   
• 属性动画作用于图层的某个单一属性,并指定了它的一个目标值,或一连串将要做动画的值
• 属性动画分两种:
1.基础
2.关键帧
• 基础动画:(通过CALayer的实例方法addAnimation: forKey:给图层添加显示动画)
• CABasicAnimation-->CAPropertyAnimation-->CAAnimation(Core Animation所有动画类型的抽象基类) 
   
• CAAnimation提供一个计时函数,一个委托(用于反馈动画状态),一个removedOnCompletion(用于标识动画是否该在结束后自动释放,默认YES) 
     
• 同时实现了一些协议,包括CAAction(允许CAAnimation的子类可以提供图层行为)以及CAMediaTiming(图层时间)
• CAPropertyAnimation通过指定动画的keyPath作用于一个单一属性,
• CAAnimation通常应用于一个指定的CALayer,于是这里指的也就是一个图层的keyPath,实际上它是一个关键路径,而不仅仅是一个属性名称 
     
• 这意味着动画不仅可以作用于图层本身属性,还包含它子成员的属性
• CABasicAnimation(从一个值改变到另一个值的基础动画类型,作用于单一属性)继承于CAPropertyAnimation,并且添加了如下属性: 
     
• id fromValue:动画开始之前属性的值
• id toValue:动画结束之后的值
• id byValue:动画执行过程中需要改变多少的值
• 被定义成id类型而不是一些具体的类型是因为属性动画可以用作很多不同种的属性类型,包括数字类型,矢量,变换矩阵,甚至是颜色或者图片
• 为防止冲突,不能一次性同时指定这三个值,不然Core Animation就不知道最终结果
• 用于CAPropertyAnimation的一些类型转换: 
     
• Type            | Object Type            | Code Example
• CGFloat        | NSNumber              | id obj = @(float);
• CGPoint        | NSValue                 | id obj = [NSValue valueWithCGPoint:point);
• CGSize      | NSValue                 | id obj = [NSValue valueWithCGSize:size);
• CGRect         | NSValue                 | id obj = [NSValue valueWithCGRect:rect);
• CATransform3D | NSValue             | id obj = [NSValue valueWithCATransform3D:transform);
• CGImageRef  | id                          | id obj = (__bridge id)imageRef;
• CGColorRef   | id                            | id obj = (__bridge id)colorRef;
• 示范例子: 
       
• self.colorLayer=[CALayer layer];
• self.colorLayer.frame=CGRectMake(50.0f, 50.0f, 100.0f, 100.0f);
• self.colorLayer.backgroundColor=[UIColor bulueColor].CGColor;
• [self.myView.layer addSublayer:self.colorLayer];
• -(void)changeColor{

• CGFloat red = arc4random() / (CGFloat)INT_MAX;
• CGFloat green = arc4random() / (CGFloat)INT_MAX;
• CGFloat blue = arc4random() / (CGFloat)INT_MAX;
• UIColor *color = [UIColor colorWithRed:red green:green blue:blue alpha:1.0];
• //create a basic animation
• CABasicAnimation *animation = [CABasicAnimation animation];
• animation.keyPath = @"backgroundColor";
• animation.toValue = (__bridge id)color.CGColor;
• //apply animation to layer
• [self.colorLayer addAnimation:animation forKey:nil];/*重点*/
• }
• /*
• 该例子中,当图层动画过渡到一个新颜色,动画结束后又立即变回原始值,因为动画并没有改变图层的模型,而只是呈现
• 我们从来没有改变过backgroundColor属性,所以图层就返回到原始的颜色
• */
• 于是在动画创建之前插入如下代码,就可以解决问题: 
       
• animation.fromValue = (__bridge id)self.colorLayer.backgroundColor;
• self.colorLayer.backgroundColor = color.CGColor;
• /*
• 但是,如果该动画在执行以上两行代码时已经在进行,那么我们需要从呈现图层那里去获取fromValue,而不是模型图层;
• 另外,由于这里的图层并不是UIView关联的图层,我们需要用CATransaction来禁用隐式动画行为,否则显式动画会受干扰。
• */
• 因这两个问题再度更新代码: 
       
• CALayer *layer = self.colorLayer.presentationLayer?self.colorLayer.presentationLayer:self.colorLayer;
• animation.fromValue = (__bridge id)layer.backgroundColor;
• [CATransaction begin];
• [CATransaction setDisableActions:YES];
• self.colorLayer.backgroundColor = color.CGColor;
• [CATransaction commit];
• /*
• 若每个动画都这么修改,代码将会非常臃肿,
• 不过可以通过创建一个可复用代码,封装成一个方法避免每次动画重复那些臃肿代码(仍是下册啊)
• */
• 示范例子: 
       
• - (void)applyBasicAnimation:(CABasicAnimation *)animation toLayer:(CALayer *)layer{

• //set the from value (using presentation layer if available)
• animation.fromValue = [layer.presentationLayer ?: layer valueForKeyPath:animation.keyPath];
• //update the property in advance
• //note: this approach will only work if toValue != nil
• [CATransaction begin];
• [CATransaction setDisableActions:YES];
• [layer setValue:animation.toValue forKeyPath:animation.keyPath];
• [CATransaction commit];
• //apply animation to layer
• [layer addAnimation:animation forKey:nil];
• }
• - (void)changeColor{

• //create a new random color
• CGFloat red = arc4random() / (CGFloat)INT_MAX;
• CGFloat green = arc4random() / (CGFloat)INT_MAX;
• CGFloat blue = arc4random() / (CGFloat)INT_MAX;
• UIColor *color = [UIColor colorWithRed:red green:green blue:blue alpha:1.0];
• //create a basic animation
• CABasicAnimation *animation = [CABasicAnimation animation];
• animation.keyPath = @"backgroundColor";
• animation.toValue = (__bridge id)color.CGColor;
• //apply animation without snap-back
• [self applyBasicAnimation:animation toLayer:self.colorLayer];
• }
• 以上例子是在动画开始之前去更新目标属性,接下来通过在动画完全结束或者取消的时候更新属性,
• 通过实现CAAnimationDeleagte协议的-animationDidStop:finished:方法知道一个显式动画何时结束(虽然通过CATransaction完成块也可以检测到动画的完成,但是不应如此实现,因为显式动画和事务并没有太多联系)
• 示范例子: 
       
• self.colorLayer = [CALayer layer];
• self.colorLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f);
• self.colorLayer.backgroundColor = [UIColor blueColor].CGColor;
• //add it to our view
• [self.myView.layer addSublayer:self.colorLayer];
• - (IBAction)changeColor{

• //create a new random color
• CGFloat red = arc4random() / (CGFloat)INT_MAX;
• CGFloat green = arc4random() / (CGFloat)INT_MAX;
• CGFloat blue = arc4random() / (CGFloat)INT_MAX;
• UIColor *color = [UIColor colorWithRed:red green:green blue:blue alpha:1.0];
• //create a basic animation
• CABasicAnimation *animation = [CABasicAnimation animation];
• animation.keyPath = @"backgroundColor";
• animation.toValue = (__bridge id)color.CGColor;
• animation.delegate = self;
• //apply animation to layer
• [self.colorLayer addAnimation:animation forKey:nil];
• }
• - (void)animationDidStop:(CABasicAnimation *)anim finished:(BOOL)flag{

• //set the backgroundColor property to match animation toValue
• [CATransaction begin];
• [CATransaction setDisableActions:YES];
• self.colorLayer.backgroundColor = (__bridge CGColorRef)anim.toValue;
• [CATransaction commit];
• }
• /*
• 使用该代理方法,当有多个动画时,无法在回调方法中区分是哪个动画,连唯一可能可以用来区分的anim参数都不行,因为该实例不是调用的那个实例,而是原始值的一个深拷贝,不是同一个值
• */
• 然而,我们可以通过在给CALayer图层添加动画
• 调用-addAnimation:forKey:的时候指定所添加动画的唯一标识符,
• 然后通过-animationForKey:方法找到对应动画的唯一标识符,
• 通过animationKeys获取当前动画所有键
• 可以遍历所有键与获取当前动画的唯一标识符做对比
• 还有一种更简单的方法: 
       
• 由于CAAnimation实现了KVC协议,可以用
• -setValue:forKey:(存属性)
• -valueForKey:(取属性)
• 并且接收参数为id类型,也就是可以向代理回调方法传递任何数据
• 使用KVC对动画打标签例子: 
         
• @interface ViewController ()
• @property (nonatomic, weak) IBOutlet UIImageView *hourHand;
• @property (nonatomic, weak) IBOutlet UIImageView *minuteHand;
• @property (nonatomic, weak) IBOutlet UIImageView *secondHand;
• @property (nonatomic, weak) NSTimer *timer;
• @end
• @implementation ViewController
• - (void)viewDidLoad{

• [super viewDidLoad];
• //adjust anchor points
• self.secondHand.layer.anchorPoint = CGPointMake(0.5f, 0.9f);
• self.minuteHand.layer.anchorPoint = CGPointMake(0.5f, 0.9f);
• self.hourHand.layer.anchorPoint = CGPointMake(0.5f, 0.9f);
• //start timer
• self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(tick) userInfo:nil repeats:YES];
• //set initial hand positions
• [self updateHandsAnimated:NO];
• }
• - (void)tick{

• [self updateHandsAnimated:YES];
• }
• - (void)updateHandsAnimated:(BOOL)animated{

• //convert time to hours, minutes and seconds
• NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
• NSUInteger units = NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit;
• NSDateComponents *components = [calendar components:units fromDate:[NSDate date]];
• CGFloat hourAngle = (components.hour / 12.0) * M_PI * 2.0;
• //calculate hour hand angle //calculate minute hand angle
• CGFloat minuteAngle = (components.minute / 60.0) * M_PI * 2.0;
• //calculate second hand angle
• CGFloat secondAngle = (components.second / 60.0) * M_PI * 2.0;
• //rotate hands
• [self setAngle:hourAngle forHand:self.hourHand animated:animated];
• [self setAngle:minuteAngle forHand:self.minuteHand animated:animated];
• [self setAngle:secondAngle forHand:self.secondHand animated:animated];
• }
• - (void)setAngle:(CGFloat)angle forHand:(UIView *)handView animated:(BOOL)animated{

• //generate transform
• CATransform3D transform = CATransform3DMakeRotation(angle, 0, 0, 1);
• if (animated) {

• //create transform animation
• CABasicAnimation *animation = [CABasicAnimation animation];
• [self updateHandsAnimated:NO];
• animation.keyPath = @"transform";
• animation.toValue = [NSValue valueWithCATransform3D:transform];
• animation.duration = 0.5;
• animation.delegate = self;
• [animation setValue:handView forKey:@"handView"];//关键所有,传值所需
• [handView.layer addAnimation:animation forKey:nil];
• } else {

• //set transform directly
• handView.layer.transform = transform;
• }
• }
• - (void)animationDidStop:(CABasicAnimation *)anim finished:(BOOL)flag{

• //set final position for hand view
• UIView *handView = [anim valueForKey:@"handView"];
• handView.layer.transform = [anim.toValue CATransform3DValue];
• }
• /*
• 以上代码在真机上测试,在-animationDidStop:finished:委托方法调用之前,指针会迅速返回到原始值,
• 因为回调方法在动画完成之前已经被调用了
• 可以通过fillMode属性解决问题,在动画之前设置它比在动画结束之后更新属性更加方便,下章详讲
• */
• 关键帧动画: 
   
• CAKeyframeAnimation-->CAPropertyAnimation-->CAAnimation(Core Animation所有动画类型的抽象基类) 
     
• CAKeyframeAnimation: 
       
• 作用于单一的一个属性,于CABasicAnimation不同,它不限制设置一个起始和结束的值,可以根据一连串随意的值做动画。
• 使用例子: 
         
• /*使用CAKeyframeAnimation应用一系列颜色变化*/
• -(void)changeColor{

• CAKeyframeAnimation *animation=[CAKeyframeAnimation animation];
• animation.keyPath=@"backgroundColor";
• animation.duration=2.0;
• animation.values=@[
• (__bridge id)[UIColor blueColor].CGColor,
• (__bridge id)[UIColor redColor].CGColor,
• (__bridge id)[UIColor greenColor].CGColor,
• (__bridge id)[UIColor blueColor].CGColor
• ];
• [self.colorLayer addAnimation:animation forKey:nil];
• }
• /*
• 为了动画的平滑性,需要开始和结束的关键帧来匹配当前属性的值(也就是开始,结束,开始动画前的属性值应保持一致)
• */
• 当然也可以在动画结束后图层的属性值与开始前的不一样,也就是经过动画之后改变图层的属性值,
• 那样的话就需要在动画启动之前手动更新属性值与最后一帧的值保持一致,与上面讨论的一样
• 默认关键帧动画以一个恒定步调在运行,每帧动画之间过渡时没有减速
• 可以通过调整缓冲,让动画看起来更自然
• CAKeyframeAnimation除了可以提供一个数组的值做动画,还可以通过使用CGPath
• path属性使用Core Graphics函数定义运动序列来绘制动画
• 示范例子: 
         
• /*使用UIBezierPath类创建一个三次贝塞尔曲线path,在CAShapeLayer专用图层上绘制CGPath,创建一个CAKeyframeAnimation应用到CALayer上*/
• UIBezierPath *bezierPath=[[UIBezierPath alloc]init];
• [bezierPath moveToPoint:CGPointMake(0,150)];
• [bezierPath addCurveToPoint:CGPointMake(300,150) controlPoint1:CGPointMake(75,0) controlPoint2:CGPointMake(225, 300)];
• CAShapeLayer *pathLayer=[CAShapeLayer layer];
• pathLayer.path=bezierPath.CGPath;
• pathLayer.fillColor=[UIColor clearColor].CGColor;
• pathLayer.strokeColor=[UIColor redColor].CGColor;
• pathLayer.lineWidth=3.0f;
• [containerView.layer addSublayer:pathLayer];
• CALayer *shipLayer=[CALayer layer];
• shipLayer.frame=CGRectMake(0,0,64,64);
• shipLayer.position=CGPointMake(0,150);
• shipLayer.contents=(__bridge id)[UIImage imageNamed:@"Ship.png"].CGImage;
• containerView.layer addSublayer:shipLayer];
• CAKeyframeAnimation *animation=[CAKeyframeAnimation animation];
• animation.keyPath=@"position";
• animation.duration=4.0;
• animation.path=bezierPath.CGPath;
• [shipLayer addAnimation:animation forKey:nil];
• /*
• 以上例子做动画的图层不会沿着曲线的切线方向旋转,所以动画看起来不真实
• 如果设置动画图层的affineTransform对运动方向做调整,会影响到其它动画
• 可以通过给CAKeyFrameAnimation设置rotationMode属性为常量kCAAnimationRotateAuto,图层将会根据曲线的切线自动旋转
• */
• //修改以上代码:
• ...
• CAKeyframeAnimation *animation=[CAKeyframeAnimation animation];
• animation.keyPath=@"position";
• animation.duration=4.0;
• animation.path=bezierPath.CGPath;
• animation.rotationMode=kCAAnimationRotateAuto;
• [shipLayer addAnimation:animation forKey:nil];
• 虚拟属性: 
   
• 做旋转图层的显式动画,可以对transform.rotation关键路径应用动画,而不是transform本身
• 示范例子: 
     
• CALayer *shaipLayer=[CALayer layer];
• shipLayer.frame=CGRectMake(0,0,128,128);
• shipLayer.position=CGPointMake(150,150);
• shipLayer.contents=(__bridge id)[UIImage imageNamed:@"Ship.png"].CGImage;
• [containerView.layer addSublayer:shipLayer];
• CABasicAnimation *animation=[CABasicAnimation animation];
• animation.keyPath=@"transform.rotation";
• animation.duration=2.0;
• animation.byValue=@(M_PI*2);
• [shipLayer addAnimation:animation forKey:nil];
• /*
• 用transform.rotation而不是transform做旋转动画好处如下:
• 1.可以不通过关键帧一步旋转多于180度的动画
• 2.可以用相对值而不是绝对值旋转(设置byValue而不是toValue)
• 3.可以不用创建CATransform3D,而是使用一个简单的数值指定角度(实际上是Core Animation自动地根据通过CAValueFunction函数计算的值来生成CATransform3D矩阵值更新transform属性)
• 4.不会和transform.position或者transform.scale冲突(同样是使用关键路径做独立的动画属性)
• */
• 像这里针对的关键路径transform.position就是虚拟属性,因为CATransform3D并不是一个对象,也没有复合KVC相关属性
 
• 动画组: 
   
• CAAnimationGroup 
     
• 可以把CABasicAnimation与CAKeyframeAnimation仅仅作用于单独属性的这些动画组合在一起
 
•  在父类基础上添加了animations数组属性
•  用来组合别的动画
•  UIBezierPath *bezierPath = [[UIBezierPath alloc] init];
•  [bezierPath moveToPoint:CGPointMake(0, 150)];
•  [bezierPath addCurveToPoint:CGPointMake(300, 150) controlPoint1:CGPointMake(75, 0) controlPoint2:CGPointMake(225, 300)];
•  //draw the path using a CAShapeLayer
•  CAShapeLayer *pathLayer = [CAShapeLayer layer];
•  pathLayer.path = bezierPath.CGPath;
•  pathLayer.fillColor = [UIColor clearColor].CGColor;
•  pathLayer.strokeColor = [UIColor redColor].CGColor;
•  pathLayer.lineWidth = 3.0f;
•  [self.containerView.layer addSublayer:pathLayer];
•  //add a colored layer
•  CALayer *colorLayer = [CALayer layer];
•  colorLayer.frame = CGRectMake(0, 0, 64, 64);
•  colorLayer.position = CGPointMake(0, 150);
•  colorLayer.backgroundColor = [UIColor greenColor].CGColor;
•  [self.containerView.layer addSublayer:colorLayer];
•  //create the position animation
•  CAKeyframeAnimation *animation1 = [CAKeyframeAnimation animation];
•  animation1.keyPath = @"position";
•  animation1.path = bezierPath.CGPath;
•  animation1.rotationMode = kCAAnimationRotateAuto;
•  //create the color animation
•  CABasicAnimation *animation2 = [CABasicAnimation animation];
•  animation2.keyPath = @"backgroundColor";
•  animation2.toValue = (__bridge id)[UIColor redColor].CGColor;
•  //create group animation
•  CAAnimationGroup *groupAnimation = [CAAnimationGroup animation];
•  groupAnimation.animations = @[animation1, animation2]; 
•  groupAnimation.duration = 4.0;
•  //add the animation to the color layer
•  [colorLayer addAnimation:groupAnimation forKey:nil];
• 过渡: 
   
•  由于属性动画只对图层的可动画属性起作用,所以如果要改变一个不能动画的属性(比如图片),或者从层级关系中添加或者移除图层,属性动画将不起作用。
•  于是有了过渡的概念
•  过渡不像属性动画平滑地在两个值之间做动画,而是影响到整个图层的变化
•  过渡动画先展示之前的图层外观,在通过一个交换过渡到新的外观
•  使用CATransition创建一个过渡动画
•  CATransition-->CAAnimation
•  NSString type:过渡效果
•  kCATransitionFade(默认,淡入淡出) 
•  kCATransitionMoveIn (从顶部滑动进入,动画效果不把旧图层推走)
•  kCATransitionPush (从边缘滑动进来,动画效果把旧图层从另一侧推出)
•  kCATransitionReveal(把原始的图层滑动出去来显示新的外观,而不是把新的图层滑动进入)
•  NSString subtype:控制过渡效果的动画方向
•  kCATransitionFromRight 
•  kCATransitionFromLeft 
•  kCATransitionFromTop 
•  kCATransitionFromBottom
•  以上两个属性用于标识变换效果
•  self.images = @[[UIImage imageNamed:@"Anchor.png"],
•  [UIImage imageNamed:@"Cone.png"],
•  [UIImage imageNamed:@"Igloo.png"],
•  [UIImage imageNamed:@"Spaceship.png"]
•  ];
•  CATransition *transition = [CATransition animation];
•  transition.type = kCATransitionFade;
•  //apply transition to imageview backing layer
•  [self.imageView.layer addAnimation:transition forKey:nil];
•  //cycle to next image
•  UIImage *currentImage = self.imageView.image;
•  NSUInteger index = [self.images indexOfObject:currentImage];
•  index = (index + 1) % [self.images count];
•  self.imageView.image = self.images[index];
•  +transitionFromView:toView:duration:options:completion:
•  +transitionWithView:duration:options:animations:
•  UIViewAnimationOptionTransitionFlipFromLeft 
•  UIViewAnimationOptionTransitionFlipFromRight
•  UIViewAnimationOptionTransitionCurlUp 
•  UIViewAnimationOptionTransitionCurlDown
•  UIViewAnimationOptionTransitionCrossDissolve 
•  UIViewAnimationOptionTransitionFlipFromTop 
•  UIViewAnimationOptionTransitionFlipFromBottom
• 隐式过渡: 
   
•  对于非UIView自带关联的CALayer,当设置了CALayer的content属性时,改变该属性,是会带有CATransition的淡入淡出隐式动画的。
• 对图层树的动画: 
   
•  过渡动画CATransition并不一定要作用于指定的图层属性,也就是说可以在即使不能准确得知改变了什么的情况下对图层做动画,例如:
•  在不知道UIViewController内部的视图层级的情况下对两个不同的实例做过渡动画。
•  确保CATransition添加到的图层在过渡动画发生时不会在树状结构中被移除,
•  否则CATransition将会和图层一起被移除。
•  一般来说,你只需要将动画添加到被影响图层的superlayer。
•  @implementation AppDelegate
•  - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{

•  self.window = [[UIWindow alloc] initWithFrame: [[UIScreen mainScreen] bounds]];
•  UIViewController *viewController1 = [[FirstViewController alloc] init];
•  UIViewController *viewController2 = [[SecondViewController alloc] init];
•  self.tabBarController = [[UITabBarController alloc] init];
•  self.tabBarController.viewControllers = @[viewController1, viewController2];
•  self.tabBarController.delegate = self;
•  self.window.rootViewController = self.tabBarController;
•  [self.window makeKeyAndVisible];
•  return YES;
•  }
•  - (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController{

•  //set up crossfade transition
•  CATransition *transition = [CATransition animation];
•  transition.type = kCATransitionFade;
•  //apply transition to tab bar controller's view
•  [self.tabBarController.view.layer addAnimation:transition forKey:nil];
•  }
•  @end
• 自定义动画: 
   
•  把截屏视图置于原始视图之上,遮住真实视图的所有变化,重新创建一个简单的过渡效果
•  CALayer的-renderInContext:方法,把它绘制到Core Graphics的上下文中捕获当前内容的图片
•  //preserve the current view snapshot
•  UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, YES, 0.0);
•  [self.view.layer renderInContext:UIGraphicsGetCurrentContext()];
•  UIImage *coverImage = UIGraphicsGetImageFromCurrentImageContext();
•  //insert snapshot view in front of this one
•  UIView *coverView = [[UIImageView alloc] initWithImage:coverImage];
•  coverView.frame = self.view.bounds;
•  [self.view addSubview:coverView];
•  //update the view (we'll simply randomize the layer background color)
•  CGFloat red = arc4random() / (CGFloat)INT_MAX;
•  CGFloat green = arc4random() / (CGFloat)INT_MAX;
•  CGFloat blue = arc4random() / (CGFloat)INT_MAX;
•  self.view.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0];
•  //perform animation (anything you like)
•  [UIView animateWithDuration:1.0 animations:^{

•  //scale, rotate and fade the view
•  CGAffineTransform transform = CGAffineTransformMakeScale(0.01, 0.01);
•  transform = CGAffineTransformRotate(transform, M_PI_2);
•  coverView.transform = transform;
•  coverView.alpha = 0.0;
•  } completion:^(BOOL finished) {

•  //remove the cover view now we're finished with it
•  [coverView removeFromSuperview];
•  }];
• 在动画过程中取消动画: 
   
•  - (CAAnimation *)animationForKey:(NSString *)key;
•  通过key在添加动画之后检索一个动画,主要用来检测动画的属性,或者判断是否被添加到当前图层中
•  - (void)removeAnimationForKey:(NSString *)key;
•  - (void)removeAllAnimations;
•  提供了移除一个图层中的动画方法,动画一旦被移除,图层外观立刻更新到当前模型图层的值而不是更新到呈现图层的值
•  默认动画结束后自动被移除,可以通过设置removedOnCompletion属性为NO改变默认动作完成后移除动画
•  动画代理方法-animationDidStop:finished:中的flag参数表明动画是自然结束(YES)还是被打断(NO)
•  @interface ViewController ()
•  @property (nonatomic, weak) IBOutlet UIView *containerView;
•  @property (nonatomic, strong) CALayer *shipLayer;
•  @end
•  @implementation ViewController
•  - (void)viewDidLoad{

•  [super viewDidLoad];
•  //add the ship
•  self.shipLayer = [CALayer layer];
•  self.shipLayer.frame = CGRectMake(0, 0, 128, 128);
•  self.shipLayer.position = CGPointMake(150, 150);
•  self.shipLayer.contents = (__bridge id)[UIImage imageNamed: @"Ship.png"].CGImage;
•  [self.containerView.layer addSublayer:self.shipLayer];
•  }
•  - (IBAction)start{

•  //animate the ship rotation
•  CABasicAnimation *animation = [CABasicAnimation animation];
•  animation.keyPath = @"transform.rotation";
•  animation.duration = 2.0;
•  animation.byValue = @(M_PI * 2);
•  animation.delegate = self;
•  [self.shipLayer addAnimation:animation forKey:@"rotateAnimation"];
•  }
•  - (IBAction)stop{

•  [self.shipLayer removeAnimationForKey:@"rotateAnimation"];
•  }
•  - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{

•  //log that the animation stopped
•  NSLog(@"The animation stopped (finished: %@)", flag? @"YES": @"NO");
•  }
•  @end