iOS |知识点整理(7)

953 阅读25分钟

延续上一篇iOS |知识点整理(6)

关于下面动画的矩阵变换

4.gif

[CATransaction begin];
CGFloat belowGap=50;
CGFloat startAngle=atan(belowGap/(_cview.frame.size.width/2.0))+20*M_PI/180;
CGFloat totalAngle=M_PI-2*startAngle;
CGFloat gapAngle=totalAngle/(arr.count-1);
for(int i=0;i<arr.count;i++){
  UIView* item=arr[i];
  CGFloat angle=-M_PI/2+startAngle+gapAngle*i;
  CGFloat dis=TESTFanViewController_Item_Height-TESTFanViewController_VisibleHeigh-TESTFanViewController_Item_Height*0.1-belowGap;
  CAKeyframeAnimation* keyAnim=[CAKeyframeAnimation animationWithKeyPath:@"transform"];
  keyAnim.duration=0.3;
  CATransform3D endTransform;
  if(CATransform3DEqualToTransform(item.layer.transform,CATransform3DIdentity)){
#if 0         //第一种变换,稍复杂
      CGFloat len=item.layer.position.x-_cview.bounds.size.width/2.0f;
      CGFloat side1=len/tan(angle)+dis;
      CGFloat side2=len/sin(angle);
      CATransform3D transform1=CATransform3DMakeTranslation(0, -side1, 0);
      CATransform3D transform2=CATransform3DRotate(transform1, angle, 0, 0, 1);
      CATransform3D transform3=CATransform3DTranslate(transform2,0, side2, 0);
#else      //第二种变换,较简单
      CGFloat len=_cview.bounds.size.width/2.0f-item.layer.position.x;
      CATransform3D transform1=CATransform3DMakeTranslation(0, -dis, 0);
      CATransform3D transform2=CATransform3DTranslate(transform1,len,0, 0);
      CATransform3D transform3=CATransform3DRotate(transform2, angle, 0, 0, 1);
#endif
      keyAnim.values=@[
                       [NSValue valueWithCATransform3D:CATransform3DIdentity],
                       [NSValue valueWithCATransform3D:transform3]
                       ];
      endTransform=transform3;
  }
  else{

      keyAnim.values=@[
                       [NSValue valueWithCATransform3D:item.layer.transform],
                       [NSValue valueWithCATransform3D:CATransform3DIdentity]
                       ];
      endTransform=CATransform3DIdentity;
  }
  [item.layer addAnimation:keyAnim forKey:@"transform"];
  item.layer.transform=endTransform;
}
[CATransaction commit];

第一种:把anchorPoint,上移->旋转->下移

37127A41-9D50-4014-BB84-BDE25D1B61A5.png

第二种:把anchorPoint移动到相应的位置,然后再旋转,较简单

关于不同view同时开始不同的动画

  [CATransaction begin];
  [CATransaction disableActions];
  //你的动画代码
  [CATransaction commit];

当然,如果要对同一个view添加不同的动画,可以使用CAAnimationGroup

关于圆弧的绘制方向问题

如果当前的context是UIKit坐标系
//下面直接设置clockwise为YES
UIBezierPath* path1=[UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:0 endAngle:angle clockwise:YES];

//而下面需要设置clockwise为NO ,因为毕竟相对于Qutarz2D坐标系,坐标系已经被翻转. CGContextAddArc(ctx, center.x, center.y,radius,angle, angle*2.0, NO);


CAKeyframeAnimation and timingFunction

CAKeyFrameAnimation中时间函数是不起作用的

CAKeyframeAnimation ignores timingFunction (you need to use keyTimes). If you prefer to just using aCAMediaTimingFunction you could add the CAKeyframeAnimation to a CAAnimationGroup. The timing function of the CAKeyframeAnimation will take precedence over the keyframe animation’s timings.


矩阵变换的理解

- (void)viewDidLoad
{
    [super viewDidLoad]; //create a new transform
    CGAffineTransform transform = CGAffineTransformIdentity; 
    transform = CGAffineTransformScale(transform, 0.5, 0.5); //scale by 50%
    transform = CGAffineTransformRotate(transform, M_PI / 180.0 * 30.0); //rotate by 30 degrees
    transform = CGAffineTransformTranslate(transform, 200, 0);//translate by 200 points
    //apply transform to layer
     self.layerView.layer.affineTransform = transform;
}

对于上述变换,我们可以理解了,首先将坐标系缩小50%,然后再整个坐标系再旋转30度,然后整个再向右移动200,注意这时移动的方向和刻度都已经发生了变化.由于前两步的变换,坐标系的方向及刻度都发生了变化,所以这时方块会向下运动,并且只移动了100.

图片向右边发生了平移,但并没有指定距离那么远(200像素),另外它还有点向下发生了平移。原因在于当你按顺序做了变换,上一个变换的结果将会影响之后的变换,所以200像素的向右平移同样也被旋转了30度,缩小了50%,所以它实际上是斜向移动了100像素。

这意味着变换的顺序会影响最终的结果,也就是说旋转之后的平移和平移之后的旋转结果可能不同。

5.4.jpeg


UIKit到Qutarz2D的矩阵变换

  //将UIKit坐标系变换成Quartz2D坐标系
  CGAffineTransform flip = CGAffineTransformMakeScale(1.0, -1.0);
  CGAffineTransform flipThenShift = CGAffineTransformTranslate(flip,0,-inputHeight);
  CGContextConcatCTM(context, flipThenShift);

  //将Qutarz2D坐标系变换成UIKit坐标系
  CGAffineTransform flip = CGAffineTransformMakeScale(1.0, -1.0);
  CGAffineTransform flipThenShift = CGAffineTransformTranslate(flip,0,-inputHeight);
  CGContextConcatCTM(context, flipThenShift);
如果想让一个点在矩阵变换的时候,它的绝对位置不发生变化,那么在变换坐标系的同时,也对那那个点施以同样的矩阵变换,那么这个. 也就是把这个点的坐标变换成别一个坐标系点的坐标,但其位置不变.

矩阵变换不影响CALayer的postion及UIView的center

矩阵变换不影响CALayer的postion及UIView的center,如标题.

如果要计算的话,只能根据具体情况来计算了.


关于下面效果中旋转角度的计算

NO79IP0ZB-DO-OFDXHH--LY.gif

先通过下面的方法,计算的旋转的矩阵变换:

055D4BDB-31A5-4ACB-9338-713DFB7DD454.png

然后,再计算出translate变换, 然后通过keyframe动画,实现上面的效果 下面动画的总体思路是,通过对view进行矩阵变换,得到view的不同姿势,然后将这些不同的姿势作为关键帧动画的keyframe,就行了. 下面的代码是实现往上运动的效果:

 //同时调整anchorPoint和position,这样使得位置不发生变化.
    _tipLabel.layer.position=CGPointMake(_tipLabel.frame.origin.x+_tipLabel.frame.size.width, _tipLabel.layer.position.y);
    _tipLabel.layer.anchorPoint=CGPointMake(1, 0.5);
    CGFloat dis=((_txtField.frame.size.height-_tipLabel.frame.size.height)/2.0f+_tipLabel.frame.size.height/2.0f);
    _dis=dis;
//计算出旋转角度
    CGFloat angle=asin(dis/_tipLabel.frame.size.width);
    _angle=angle;
    CATransform3D trans1=CATransform3DMakeRotation(angle,0, 0, 1);
    CATransform3D trans2=CATransform3DTranslate(CATransform3DIdentity,0 , -dis, 0);
    CAKeyframeAnimation* keyAnim=[CAKeyframeAnimation animationWithKeyPath:@"transform"];
    [keyAnim setValues:@[
                         [NSValue valueWithCATransform3D:CATransform3DIdentity],
                         [NSValue valueWithCATransform3D:trans1],
                         [NSValue valueWithCATransform3D:trans2]
                         ]];
    keyAnim.duration=0.3f;
    keyAnim.calculationMode=kCAAnimationCubic;
    keyAnim.removedOnCompletion=NO;
    keyAnim.fillMode=kCAFillModeForwards;
  //注意下面对于CAKeyframeAnimation来说,其时间函数是无效的,如果想要控制动画的速度,只能通过设置
 许多的关键帧,通过控制各关键帧之间数据变化的函数,来控制整个动画的时间效果.
    keyAnim.timingFunction=[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    _tipLabel.layer.transform=trans2;
    [_tipLabel.layer addAnimation:keyAnim forKey:@"transform"];
    _txtField.layer.borderColor=[UIColor redColor].CGColor;

关于矩阵变换的计算

对于矩阵变换,将scale变换理解成对于当前坐系的缩放,对于rotation变换,理解成对于当前坐标系的旋转,对于translation变换,理解成对于当前坐标系的平移. 注意是对于当前坐标系的,比如当我先旋转,然后再平移,那么我平移的方向是旋转之后的坐标称的方向.

CGFloat thirtyDegrees = 30.0 * M_PI / 180.0;
CGFloat distanceToRotationPoint = _av.bounds.size.width;

CGAffineTransform rotation1=CGAffineTransformIdentity;
rotation1=CGAffineTransformTranslate(rotation1, -distanceToRotationPoint, 0);
rotation1=CGAffineTransformRotate(rotation1, -thirtyDegrees);
rotation1=CGAffineTransformTranslate(rotation1, distanceToRotationPoint, 0);
_av.transform=rotation1;

NSLog(@"av:%@",NSStringFromCGPoint(_av.center));
NSLog(@"av_frame:%@",NSStringFromCGRect(_av.frame));
NSLog(@"outer:%@",NSStringFromCGPoint(_outer.center));
NSLog(@"outer_frame:%@",NSStringFromCGRect(_outer.frame));
CGPoint center=CGPointMake(_av.frame.origin.x+_av.frame.size.width/2.0f,_av.frame.origin.y+_av.frame.size.height/2.0f);
//通过勾股定理求出view变换前后的中心点的距离   
NSLog(@"hyper:%lf",hypot(_outer.center.x-center.x, _outer.center.y-center.y));
    CGFloat sideLen=distanceToRotationPoint;
   //用余弦定理,就是view变换前后的中心点的距离
    NSLog(@"cos:%lf",sqrt(2*sideLen*sideLen*(1-cos(thirtyDegrees))));

输出:

2015-12-26 17:04:21.024 TEST_AnchorP_Position[93406:2133797] av:{156, 329}
2015-12-26 17:04:21.026 TEST_AnchorP_Position[93406:2133797] av_frame:{{-12.07695154586736, 93.574374157795958}, {271.84609690826528, 230.85125168440817}}
2015-12-26 17:04:21.027 TEST_AnchorP_Position[93406:2133797] outer:{156, 329}
2015-12-26 17:04:21.027 TEST_AnchorP_Position[93406:2133797] outer_frame:{{36, 265}, {240, 128}}
2015-12-26 17:04:21.028 TEST_AnchorP_Position[93406:2133797] hyper:124.233142

2015-12-26 17:04:21.028 TEST_AnchorP_Position[93406:2133797] cos:124.233142

7F566531-2ED5-40BA-9362-5E186DFA49B1.png

9C8C01C5-2E6E-43A5-9AD0-21D9DF90BA37.png

动画属性简写

注意在使用下面的属性之前,要在前面加个Transform,如: Transform.rotation.x

Screen-shot-2010-01-25-at-1.33.58-PM.png

关于绘制线段的宽度分布

A9F864F5-A328-4A80-BFF5-C95E19766A56.png

在上面的图白色圆的半径是100 ,而线段的宽度也是100, 那么这条线段的一半在白色圆内,一半在白色圆外, 那么看起来白色圆的半径只有50了. 也就是说线段宽度的分布,是一半在其绘制位置的内部,一半在其绘制位置外部

CGRect rect=CGRectMake(0, 0, 200, 200);
_circleLayer.frame=rect;
_circleLayer.position=self.view.center;
_centerPoint=CGPointMake(100, 100);
[self.view.layer addSublayer:_circleLayer];
_circleLayer.contents=(__bridge id)[self getImage:_circleLayer];
CGPoint p=CGPointMake(100, 150);
UIBezierPath* bPath=[UIBezierPath bezierPathWithArcCenter:p radius:100 startAngle:0 endAngle:2*M_PI clockwise:YES];
_shaperLayer=[CAShapeLayer layer];
_shaperLayer.path=bPath.CGPath;
_shaperLayer.lineWidth=100;
_shaperLayer.strokeColor=[UIColor blackColor].CGColor;
_shaperLayer.strokeStart=0;
_shaperLayer.strokeEnd=0;
_shaperLayer.fillColor=[UIColor clearColor].CGColor;
CABasicAnimation* anim=[CABasicAnimation animationWithKeyPath:@"strokeEnd"];
anim.removedOnCompletion=NO;
anim.fillMode=kCAFillModeForwards;
anim.byValue=@1.0;
anim.duration=8.f;
anim.timingFunction=[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
_shaperLayer.frame=rect;
_circleLayer.mask=_shaperLayer;
[_shaperLayer addAnimation:anim forKey:@"strokeEnd"];

关于显式动画的结束callback

使用隐式动画的时候,我们可以在CATransaction完成块中检测到动画的完成。但是这种方式并不适用于显式动画,因为这里的动画和事务并没太多关联

那么为了知道一个显式动画在何时结束,我们需要使用一个实现了CAAnimationDelegate协议的delegate

CAAnimationDelegate在任何头文件中都找不到,但是可以在CAAnimation头文件或者苹果开发者文档中找到相关函数。在这个例子中,我们用-animationDidStop:finished:方法在动画结束之后来更新图层的backgroundColor。

我们来扩展之前旋转飞船的示例,这里添加一个按钮来停止或者启动动画。这一次我们用一个非nil的值作为动画的键,以便之后可以移除它。-animationDidStop:finished:方法中的flag参数表明了动画是自然结束还是被打断,我们可以在控制台打印出来。如果你用停止按钮来终止动画,它会打印NO,如果允许它完成,它会打印YES。 ref:[008]显式动画


覆盖隐式动画

To avoid this, save the original model value before changing it and explicitly set the animation’s fromValue:

// Save the original value
CGFloat originalY = layer.position.y;

// Change the model value
layer.position = CGPointMake(layer.position.x, 300.0);

CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position.y"];

// Now specify the fromValue for the animation because
// the current model value is already the correct toValue
animation.fromValue = @(originalY);
animation.duration = 1.0;

// Use the name of the animated property as key
// to override the implicit animation
[layer addAnimation:animation forKey:@"position"];

Override the Implicit Animation

Note a third modification I made to the code: The key passed to the -addAnimation:forKey: method is usually an arbitrary string (or even nil) you can use to later identify a specific animation. Here, I used the name of the property our animation changes (@"position") for the key argument. This makes sure that any implicit animation that has been created by the modification of the model value gets overridden with our explicit animation. Had we used a different key for the explicit animation, it’s possible that Core Animation would have to deal with two conflicting animations at the same time

ref:[005]Prevent Layers From Snapping Back to Original Values When Using Explicit CAAnimations – Ole Begemann


iOS 使用Quartz 2D画虚线

iOS 使用Quartz 2D画虚线 - Bannings的专栏 - 博客频道 - CSDN.NET


draw NSString

// Create text attributes
NSDictionary *textAttributes = @{NSFontAttributeName: [UIFont systemFontOfSize:18.0]};
// Create string drawing context
NSStringDrawingContext *drawingContext = [[NSStringDrawingContext alloc] init];
drawingContext.minimumScaleFactor = 0.5; // Half the font size
CGRect drawRect = CGRectMake(0.0, 0.0, 200.0, 100.0);
[string drawWithRect:drawRect
             options:NSStringDrawingUsesLineFragmentOrigin
          attributes:textAttributes
             context:drawingContext];

NSFont *font = [NSFont fontWithName:@"Palatino-Roman" size:14.0];
NSDictionary *attrsDictionary =
[NSDictionary dictionaryWithObjectsAndKeys:
                              font, NSFontAttributeName,
                              [NSNumber numberWithFloat:1.0], NSBaselineOffsetAttributeName, nil];

父view的transform对子view的影响

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"before transform.....");
    NSLog(@"rect_child:%@",NSStringFromCGRect(_miview.frame));
    NSLog(@"rect_parent:%@",NSStringFromCGRect(_mview.frame));
    _mview.transform=CGAffineTransformMakeScale(0.1, 0.1);
    NSLog(@"after transform.....");
    NSLog(@"rect_child:%@",NSStringFromCGRect(_miview.frame));
    NSLog(@"rect_parent:%@",NSStringFromCGRect(_mview.frame));
}

89768F42-37FE-41E9-8199-EF45322DB686.png

变换后:

577DE199-B050-45B4-8A6C-4DF668412037.png

输出:

2015-12-18 14:21:37.979 TEST_CoreText1[97594:206517] before transform.....
2015-12-18 14:21:37.980 TEST_CoreText1[97594:206517] rect_child:{{39, 21}, {162, 86}}
2015-12-18 14:21:37.980 TEST_CoreText1[97594:206517] rect_parent:{{26, 213}, {240, 128}}
2015-12-18 14:21:37.980 TEST_CoreText1[97594:206517] after transform.....
2015-12-18 14:21:37.980 TEST_CoreText1[97594:206517] rect_child:{{39, 21}, {162, 86}}
2015-12-18 14:21:37.981 TEST_CoreText1[97594:206517] rect_parent:{{134, 270.6}, {24, 12.8}}

计算旋转后的frame大小

NSLog(@"%@",NSStringFromCGRect(self.view.bounds));
UIBezierPath* b=[UIBezierPath bezierPathWithRect:self.view.bounds];
[b applyTransform:CGAffineTransformMakeRotation(M_PI_2)];
NSLog(@"%@",NSStringFromCGRect(CGPathGetBoundingBox(b.CGPath)));

还有一种方法是直接计算:

stackoverflow.com/questions/9…


UIView和CALayer的frame都受到transform的影响

NSLog(@"%@",NSStringFromCGRect(_bview.layer.frame));
_bview.layer.transform=CATransform3DMakeRotation(M_PI/4.0f, 0, 0, 1);
NSLog(@"%@",NSStringFromCGRect(_bview.layer.frame));

NSLog(@"%@",NSStringFromCGRect(_bview.frame));
_bview.transform=CGAffineTransformMakeRotation(M_PI/4.0f);
NSLog(@"%@",NSStringFromCGRect(_bview.frame));

关于CGContextAddArc中的顺时针和逆时针

UIGraphicsBeginImageContextWithOptions(layer.bounds.size, NO, [UIScreen mainScreen].scale);
CGContextRef ctx=UIGraphicsGetCurrentContext();
int totalSperate=16;
CGFloat startAngle=0;
for(int i=0;i<totalSperate ;i++){
    UIColor* color=[UIColor colorWithRed:(arc4random()%255)/255.0 green:(arc4random()%255)/255.0  blue:(arc4random()%255)/255.0 alpha:1];
    NSLog(@"color:%@,angle:%lf",color,startAngle);
    CGContextBeginPath(ctx);
    CGContextMoveToPoint(ctx, _centerPoint.x, _centerPoint.y);
//注意下面的代码中,由于Ctx已经进行了坐标系翻转,那么下面API中标注的顺时针,在实际绘图的时候,会变成逆时针.
    CGContextAddArc(ctx, _centerPoint.x, _centerPoint.y,100,startAngle,startAngle+M_PI*2/totalSperate, 0);
    CGContextSetFillColorWithColor(ctx, color.CGColor);
    CGContextClosePath(ctx);
    CGContextFillPath(ctx);
    startAngle+=M_PI*2/totalSperate;
}
CGImageRef temp=UIGraphicsGetImageFromCurrentImageContext().CGImage;
UIGraphicsEndImageContext();

CALayer上的CABasicAnimation没有frame动画

由于frame是bounds,positon,transform三者合成而来的,所以在CALayer上,如果要对frame做动画,那么就得 同时改变下bounds和frame.

那么如果我单独改变bounds的话,由于position没有发生变化,会造成layer不能完全在父layer中显示出来. 注意:如果我改变layer的frame的话,那么position会根据anchorPoint变成相应的值,

如果我只是改变了layer的bounds的话,那么position的值不会发生变化. 上面说过frame是合成的属性,而bounds不是.

-(void)didMoveToSuperview{
    [super didMoveToSuperview];
    CGFloat width=self.frame.size.width;
    CGFloat height=10;
    CALayer* layer=[CALayer layer];
    layer.backgroundColor=[UIColor redColor].CGColor;
    [self.layer addSublayer:layer];
    CABasicAnimation* a=[CABasicAnimation animationWithKeyPath:@"bounds"];
    a.fromValue=[NSValue valueWithCGRect:CGRectMake(0, 0, 0, height)];
    a.toValue=[NSValue valueWithCGRect:CGRectMake(0, 0,width , height)];

    CABasicAnimation* b=[CABasicAnimation animationWithKeyPath:@"position"];
    b.fromValue=[NSValue valueWithCGPoint:CGPointMake(0, height/2.0f)];
    b.toValue=[NSValue valueWithCGPoint:CGPointMake(width/2.0f, height/2.0f)];

    CAAnimationGroup * group =[CAAnimationGroup animation];
    group.removedOnCompletion=NO; group.fillMode=kCAFillModeForwards;
    group.animations =[NSArray arrayWithObjects:a, b, nil];
    group.duration = 0.7;
    [layer addAnimation:group forKey:@"frame"];
}

在Retain屏幕上使用UIGraphicsBeginImageContext

You need to use UIGraphicsBeginImageContextWithOptions instead of UIGraphicsBeginImageContext, so that you can specify the scale factor of the image. This will use the scale factor of the device's main screen:

UIGraphicsBeginImageContextWithOptions(imageSize, NO, 0);

This will use the scale factor of the screen containing cellImage, if cellImage is on a screen:

UIGraphicsBeginImageContextWithOptions(imageSize, NO, cellImage.window.screen.scale);

This will hardcode the scale factor:

UIGraphicsBeginImageContextWithOptions(imageSize, NO, 2);


CALayer的CATransform3D是

CATransform3D transform1 = CATransform3DIdentity;
transform1.m34 = -1 / 500.0;
CATransform3D transform2 = CATransform3DTranslate(transform1, 0, height / 2-40, 0);
transform2 = CATransform3DRotate(transform2, M_PI / 20, 1, 0, 0);
transform2 = CATransform3DTranslate(transform2, 0, -height / 2, 0);

设置CATextLayer的contentsScale,让其清晰

CGFloat midx=CGRectGetMidX(self.bounds)-3;
CGFloat midy=CGRectGetMidY(self.bounds)+3;
CATextLayer *label = [[CATextLayer alloc] init];
[label setFontSize:10];
[label setFrame:CGRectMake(midx+midx/2.0f-2, 0, 10, 10)];
[label setString:@"今"];
label.contentsScale=[[UIScreen mainScreen] scale];
[label setAlignmentMode:kCAAlignmentCenter];
[label setForegroundColor:[[UIColor whiteColor] CGColor]];
[_infoLayer addSublayer:label];

关于CAAnimation中的timeOffset的运用.

通过控制timeOffset那么,我就可以操纵动画的运行至某一个特定的状态. Starting really simple, we create a basic animation for the background color of a layer and add it to that layer. We set the speed of the layer to 0 to pause the animation.

CABasicAnimation *changeColor =
   [CABasicAnimation animationWithKeyPath:@"backgroundColor"];
changeColor.fromValue = (id)[UIColor orangeColor].CGColor;
changeColor.toValue   = (id)[UIColor blueColor].CGColor;
changeColor.duration  = 1.0; // For convenience 

[self.myLayer addAnimation:changeColor
                   forKey:@"Change color"];

self.myLayer.speed = 0.0; // Pause the animation
Then in the action method for when the slider is dragged we set the current value of the slider (also configured to go from 0 to 1) as the time offset of the layer

- (IBAction)sliderChanged:(UISlider *)sender {
    self.myLayer.timeOffset = sender.value; // Update "current time"
}

This gives the effect that as we drag the slider the current value of the animation changes and updates the background color of the layer.

通过上面的代码,就可以实现,随着滑块的托动,让上面变色的效果.

4098DB77-5815-4B96-9B51-33B6AC78453B.png ref:ronnqvi.st/controlling…


What's the difference between Quartz Core, Core Graphics and Quartz 2D?

Quartz 2D is an API of the Core Graphics framework that implements drawing. Quartz Core is a framework that includes APIs for animation and image processing.

Foundation:Objective-C的核心库。写个Objective-C程序必须包含的库。提供了Objective-C中基本的数据类型和服务。他其实是和Cocoa更加亲密的,按照层次应该和Cocoa是一层。   Core Foundation: 一个接近系统的C库, 让调用方可以方便的访问系统级的内容。Foundation和Cocoa有其部分功能的封装,这就是为什么完成同一件事有多种解决方案的起源。个人感觉这个比Foundation功能更强大,毕竟更贴近底层自然效率会高一些吧。而且后面会提到,Foundation为了更好的调用Core Foundation,还得和Toll Free Bridging交互。 Quartz frameworks and their APIs

CoreGraphics.framework

  • Quartz 2D API manages the graphic context and implements drawing.
  • Quartz Services API provides low level access to the window server. This includes display hardware, resolution, refresh rate, and others. QuartzCore.framework
  • Core Animation: Objective-C API to do 2D animation.
  • Core Image: image and video processing (filters, warp, transitions).iOS 5 Quartz.frameworkOS X only
  • Image Kit: display and edit images.
  • PDF Kit: display and edit PDFs.
  • Quartz Composer: display Quartz Composer compositions.
  • QuickLookUI: preview media elements. All three frameworks use OpenGL underneath because all drawing in iOS or OS X goes through OpenGL at some point. See the section Media Layer Frameworks of the Mac OS X Technology Overview for details. Other "Quartz" technologies you may have heard of:
  • Quartz Extreme: GPU acceleration for Quartz Composer.
  • QuartzGL (aka "Quartz 2D Extreme"): GPU acceleration for Quartz 2D. These are internal implementations of GPU rendering, not APIs. They decide whether to create the window buffer in the CPU and only use OpenGL to upload as a texture (the default) or to do the whole rendering using OpenGL, which not always improves performance. You can alternate between the two using the QuartzGLEnable Info.plist setting. For an explanation see John Siracusa review of Mac OS X 10.4 Tiger, pages 13 and 14.

“Quartz” and “Core” are marketing names sprinkled over frameworks and APIs in a random manner. If they wanted to create a confusing naming mess, they succeeded.


UIBezierPath的使用

screenshot.png

绘制上面的图形,可以使用下面的代码:

UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:lowerLeftCorner];
[path addLineToPoint:beforeLeftArc];
[path addArcWithCenter:leftCircleCenter
                radius:smallRadius
            startAngle:straightLeftAngle
              endAngle:straightUpAngle
             clockwise:YES];
[path addLineToPoint:upperRightCorner];
[path addLineToPoint:beforeRightArc];
[path addArcWithCenter:rightCircleCenter
                radius:largeRadius
            startAngle:straightRightAngle
              endAngle:straightDownAngle
             clockwise:YES];
[path closePath];

也可以精简为:

UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:lowerLeftCorner];
[path addArcWithCenter:leftCircleCenter
                radius:smallRadius
            startAngle:straightLeftAngle
              endAngle:straightUpAngle
             clockwise:YES];
[path addLineToPoint:upperRightCorner];
[path addArcWithCenter:rightCircleCenter
                radius:largeRadius
            startAngle:straightRightAngle
              endAngle:straightDownAngle
             clockwise:YES];
[path closePath];

ref:Thinking like a Bézier path


关于UIBezierPath的用法

  1.当定义一个新的UIBezierPath对象的时候,current point是未定义的,你得用moveToPoint显式的指定的一下.  当你添加了一段路径以后,这段路径的最后的点,就自动成为了current point.
  2.调用closePath通过在current point和第一个点之间添加一个直线的方式,关闭路径.  当你调用moveToPoint的时候,会结束当前的subpath,开始新subpath.
  After defining the shape, you can use additional methods of this class to render the path in the current drawing context.
   When you create a new empty path object, the current point is undefined and must be set explicitly.   After adding the segment, the end point of the new segment automatically becomes the current point.
  Calling the closePath method closes a subpath by adding a straight line segment from the current point to the first point in the subpath. Calling the moveToPoint: method ends the current subpath (without closing it) and sets the starting point of the next subpath. The subpaths of a Bezier path object share the same drawing attributes and must be manipulated as a group. To draw subpaths with different attributes, you must put each subpath in its own UIBezierPath object.
After configuring the geometry and attributes of a Bezier path, you draw the path in the current graphics context using the stroke and fill methods.  
In addition to using a Bezier path object to draw shapes, you can also use it to define a new clipping region.

关于IOS系统的transform的运算顺序.

1.CGAffineTransform变换函数构造的矩阵在左边,如:

 * t' = CGAffineTransformTranslate(t,tx,ty)
 * 结果为:t' = [ 1 0 0 1 tx ty ] * t
 * t'' = CGAffineTransformTranslate(t’,tx’,ty')
 * 结果为:t'' = [ 1 0 0 1 tx’ ty' ] * [ 1 0 0 1 tx ty ] * t

对于CGContextRotateCTM也是根据上面的运算顺序进行变换的.

也就是说IOS系统中,矩阵的计算顺序,左结合,假设对于矩阵A,应用B变换.那么计算的顺序就是BA,再对这个结果应用C变换,那么结果就CB*A

要注意与CGAffineTransformConcat的区别:

CGAffineTransform t1 = CGAffineTransformMakeTranslation(0, 100);
CGAffineTransform t2 = CGAffineTransformMakeScale(.8, .8);
CGAffineTransform t3 = CGAffineTransformConcat(t2, t1);

上面的t3=t2*t1

2.对于矩阵变换的理解:


关于系统的坐标系转换

对于框架中两个方法:

- (void)drawRect:(CGRect)rect
-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx

上面的两个方法中的context都是进行过坐标系变换的,都是UIKit坐标系.

而对于UIGraphicsBeginImageContext是需要进行坐标系变换的,通过UIGraphicsGetCurrentContext()得到的是Core Graphics的坐标系,而不是Quartz2D坐标系

-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx{
    CGRect rect=CGRectMake(0, 0, 100, 100);
    CGContextRef context = ctx;
    CGPoint p = CGPointMake(rect.size.width/2.0f, rect.size.height/2.0f);
    UIGraphicsBeginImageContextWithOptions(rect.size,NO,[UIScreen mainScreen].scale);
    CGContextRef con=UIGraphicsGetCurrentContext();
    NSLog(@"Image_CTM:%@",NSStringFromCGAffineTransform(CGContextGetCTM(con)));
    NSLog(@"Layer_CTM:%@",NSStringFromCGAffineTransform(CGContextGetCTM(ctx)));
   //通过下面的变换,将当前con的Core Graphics的坐标系,重新变换为Quartz2D坐标系,因为下面CGContextClipToMask时,使用的是Core Graphics坐标系.
    CGContextRotateCTM(con, M_PI); //先旋转180度,是按照原先顺时针方向旋转的。这个时候会发现位置偏移了
    CGContextScaleCTM(con, -1, 1); //再水平旋转一下
    CGContextTranslateCTM(con,0, -100);//再把偏移掉的位置调整回来
    UIBezierPath* bpath=[UIBezierPath bezierPath];
    [[UIColor blackColor] setFill];
    [bpath moveToPoint:p];
    [bpath addArcWithCenter:p radius:p.x startAngle:0 endAngle:M_PI clockwise:YES];

    CGContextAddPath(con, bpath.CGPath);
    CGContextFillPath(con);
    CGImageRef mask = CGBitmapContextCreateImage(UIGraphicsGetCurrentContext());
    NSLog(@"image width:%ld height:%ld",CGImageGetWidth(mask),CGImageGetHeight(mask));
    UIGraphicsEndImageContext();
    CGContextClipToMask(context, rect, mask);

    CGFloat components[8]={
        1.0f,0,0,1.0f,
        1.0f,245.0/255.0f,207.0/255.0f,1.0f      //end color
    };
    CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
    CGGradientRef gradient = CGGradientCreateWithColorComponents(space, components, NULL,2);

    CGPoint p1 = CGPointMake(rect.size.width/2.0f, rect.size.height/2.0f);
    CGContextDrawRadialGradient(context, gradient, p1, 0.0f, p1, p1.x, 0);
    CGColorSpaceRelease(space);
    CGContextClosePath(context);
    CGContextFillPath(context);
}

图片显示的坐标系转换

- (void)drawRect:(CGRect)rect {
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGPoint p = CGPointMake(rect.size.width/2.0f, rect.size.height/2.0f);
    [[UIColor redColor] setStroke];
    CGContextSetLineWidth(context, 1);
    CGContextMoveToPoint(context, 0, 0);
    CGContextAddLineToPoint(context, p.x, p.y);
    CGContextStrokePath(context);
    UIImage* image=[UIImage imageNamed:@"Professortocat_v2"];
    {
        CGRect rect=CGRectMake(30,30, 50, 50);
        //    [image drawInRect:rect];//单独使用它,也可以


       //    注意下面的变换是基于其坐标系原点的.由于是在drawRect中,也就是说是基于UIKit坐标系的变换.

        //    下面的变换是不正确的,只有当图片位于[0,0]时才正确
        //    CGContextTranslateCTM(context, 0, 50);
        //    CGContextScaleCTM(context, 1.0, -1.0);
        //    CGContextDrawImage(context,r, [image CGImage]); //把图片打印在Quartz Context上面

       //下面是正确的变换,正确理解变换的顺序为1->2->3->4
        CGContextSaveGState(context);
        CGContextTranslateCTM(context, rect.origin.x, rect.origin.y);//4
        CGContextTranslateCTM(context, 0, rect.size.height);//3
        CGContextScaleCTM(context, 1.0, -1.0);//2
        CGContextTranslateCTM(context, -rect.origin.x, -rect.origin.y);//1
        CGContextDrawImage(context, rect, image.CGImage);
        CGContextRestoreGState(context);
    }
}

screenshot.png


关于UIKit与Quartz的坐标系转换

transform转换可以理解为点位置的变换或者是坐标系的变换. UIKit的坐标系的原点在左上角,而Qutarz的坐标系统的原点在左下角.

    CGContextRef imgCtx = UIGraphicsGetCurrentContext();
    //转换成UIKit的坐标系
//    CGContextRotateCTM(imgCtx, M_PI); //先旋转180度,是按照原先顺时针方向旋转的。这个时候会发现位置偏移了
//    CGContextScaleCTM(imgCtx, -1, 1); //再水平旋转一下
//    CGContextTranslateCTM(imgCtx,0, -100);//再把偏移掉的位置调整回来

通过上面的变换,就可以将quartz中的点通过上面的变换之后,其位置发生了变化,正好对应着UIKit坐标系中相关的位置..

29E20512-02D4-44A2-A9A0-561905EBD6BE.png


让图片从当前位置开始连续转动

注意一下,当additive为YES的时候,动画的状态是从(fromValue + 当前状态) 到 (toValue + 当前状态) ,仅此而已. 当additive为NO的时候,动画的状态是从fromValue到toValue.

- (IBAction)btnClick:(id)sender {
    // Change the model to the new "end value" (key path can work like this but properties don't)
    CGFloat angleToAdd   = M_PI_2; // 90 deg = pi/2
    NSString *zRotationKeyPath = @"transform.rotation.z"; // The killer of typos
    CGFloat currentAngle = [[_myview.layer valueForKeyPath:zRotationKeyPath] floatValue];
    [_myview.layer setValue:@(currentAngle+angleToAdd) forKeyPath:zRotationKeyPath];

    // Change the model to the new "end value" (key path can work like this but properties don't)
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:zRotationKeyPath];
    animation.delegate=self;
    animation.additive=YES;
    animation.duration = 1;
    // @( ) is fancy NSNumber literal syntax ...
    //注意这里fromValue是通过toValue和byValue倒推出来的,即(toValue - byValue )
    animation.toValue = @(0.0);        // model value was already changed. End at that value
    animation.byValue = @(angleToAdd); // start from - this value (it's toValue - byValue (see above))

    // Add the animation. Once it completed it will be removed and you will see the value
    // of the model layer which happens to be the same value as the animation stopped at.
    [_myview.layer addAnimation:animation forKey:@"90rotation"];
}

EF855144-0F20-4072-8476-DB21607C061E.png ref:Using CABasicAnimation to rotate a UIImageView more than once

其它方法进行连续动画:

方案1:
 if (!_isForwardCenter) {
      _isForwardCenter = YES;
      animation.fromValue = @([fromValue intValue] - [toValue intValue]);
    }
    else {
      _isForwardCenter = NO;
      animation.fromValue = @([toValue intValue] - [fromValue intValue]);
      endValue = fromValue;
    }
    animation.toValue = @(0);
    animation.additive = YES;
    animationLayer.position = CGPointMake(animationLayer.position.x, [endValue intValue]);
    animation.timingFunction = [CAMediaTimingFunction functionWithControlPoints:.5 :0 :.5 :1]; // better easing function
    static NSUInteger number = 0; //use nil key or integer, not [NSDate date] because string description only shows seconds
    NSString *key = [NSString stringWithFormat:@"ani_%lu", (unsigned long)number++];
    [animationLayer addAnimation:animation forKey:key];
  }

方案2,采用的UIView动画,注意相关option的设置:

- (void)UIKitAnimation:(BOOL)isReverse
{
  if (!isReverse) {
    [UIView animateWithDuration:1.0f
                     animations:^{
                       self.imageViewRight.center = CGPointMake(self.imageViewRight.center.x, 500);
                     }];
  }
  else
  {
    [UIView animateWithDuration:1.0f
                          delay:0
                        options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveLinear
                     animations:^{
                       self.imageViewRight.center = CGPointMake(self.imageViewRight.center.x, 88);
                     } completion:^(BOOL finished) {
                     }];
  }
}
上述方法的一般步骤是:
1.将动画设置成additive=YES
2.将layer的当前状态设置成动画的结束时的状态
3.设置动画的tovalue=@(0)
   然后设置动画的byvalue为动画结束状态与动画开始状态的相关属性的差值.

位置来回切换的动画

The problem you are facing is that the position is not the same as the origin. Anyway, you are doing a relative animation of the position so it would be easier if you skip the toValue and fromValue and instead use the byValue property (with the relative change). Your code would look like this:

CABasicAnimation *moveUp;
moveUp          = [CABasicAnimation animationWithKeyPath:@"position.y"];      
moveUp.byValue  = @(-50.0f); // or [NSNumber numberWithFloat:-50.0f] if you really need to
moveUp.duration = 1.0;
moveUp.removedOnCompletion = NO;
moveUp.fillMode = kCAFillModeBoth;
moveUp.delegate = self;
[[retestBTN layer] addAnimation:moveUp forKey:@"y"];

and

CABasicAnimation *moveDown;
moveDown            = [CABasicAnimation animationWithKeyPath:@"position.y"];
moveDown.byValue    = @(50.0f); // or [NSNumber numberWithFloat:50.0f] if you really need to
moveDown.duration   = 1.0;
moveDown.removedOnCompletion = NO;
moveDown.fillMode   = kCAFillModeForwards;
[[retestBTN layer] addAnimation:moveDown forKey:@"y"];

ref:stackoverflow.com/questions/1…


CAAnimation动画保持在最后的状态

CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position.y"]; animation.toValue = @300.0;
animation.duration=1.0;
[layeraddAnimation:animationforKey:@"positionAnimation"];

改成:

// Save the original value
CGFloat originalY = layer.position.y;
// Change the model value
layer.position = CGPointMake(layer.position.x, 300.0);
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position.y"];
// Now specify the fromValue for the animation because
// the current model value is already the correct toValue
animation.fromValue = @(originalY);
animation.duration = 1.0;
// Use the name of the animated property as key
// to override the implicit animation
[layer addAnimation:animation forKey:@"position"];

动画完成后,view会返回到其初始状态,解释如下:

1.Explicit Animations Only Affect the Presentation Layer.
2.将动画名称改为postion,可以覆盖掉相应的隐式动画.
AppKit’s implicit pattern creates a default key which may or may not be identical to the keyPath being animated. But there is one single key per property keyPath, and new animations overwrite previous ones. 
3.网上说的那种使用kCAFillModeForwards的方案,动画完成后,它的position依然没有改变,只是让动画保持在最后的状态而已.

If you search the web for this problem, you will find other “solutions”, such as setting the animation’s fillMode property to kCAFillModeForwards and at the same time setting removedOnCompletion to NO. This seems to fix the cause of the issue when in fact it only hides the symptoms. This approach basically lets the animation live forever – and show the layer in its target position as long as it lives on. However, when you query the model layer, it will still return its original value. And you may also run into problems as soon as you set up another animation for the same property

The absolute worst would be to set a layer’s model value after an animation has ended, which was something I struggled with when first learning Core Animation.

ref:Prevent Layers From Snapping Back to Original Values When Using Explicit CAAnimations – Ole Begemann


关于CAAnimation的fromValue,byValue,toValue

The fromValue, byValue and toValue properties define the values being interpolated between. All are optional, and no more than two should be non-nil. The object type should match the type of the property being animated. The interpolation values are used as follows:

Both fromValue and toValue are non-nil. Interpolates between fromValue and toValue.
fromValue and byValue are non-nil. Interpolates between fromValue and (fromValue + byValue).
byValue and toValue are non-nil. Interpolates between (toValue - byValue) and toValue.
fromValue is non-nil. Interpolates between fromValue and the current presentation value of the property.
toValue is non-nil. Interpolates between the current value of keyPath in the target layer’s presentation layer and toValue.
byValue is non-nil. Interpolates between the current value of keyPath in the target layer’s presentation layer and that value plus byValue.
All properties are nil. Interpolates between the previous value of keyPath in the target layer’s presentation layer and the current value of keyPath in the target layer’s presentation layer.

IBDesignable中加载资源时的注意事项

我自定义一个view,创建了一个xib,然后创建一个类继承UIView,而且加上了IB_DESIGNABLE使他在interface builder里能够预览,在这个类的初始化代码(initWithFrame,initWithCoder)中使用[[NSBundle mainBundle] loadNibNamed:@"CustomView" owner:self options:nil];来加载这个xib,但是这样弄以后,在手机上是能正常运行的,但是在xcode的预览里面就会报错。在代码里要写成这样,才能在xcode和运行的时候都正常: [[NSBundle bundleForClass:[self class]] loadNibNamed:@"CustomView" owner:self options:nil]; 解决方案:

As of when this question was first asked, creating an IB-designable control required packaging it in a framework target. You don't have to do that anymore — the shipping Xcode 6.0 (and later) will preview IB-designable controls from your app target, too. However, the problem and the solution are the same.

Why? [NSBundle mainBundle] returns the primary bundle of the currently running app. When you call that from a framework, you're getting a different bundle returned based on which app is loading your framework. When you run your app, your app loads the framework. When you use the control in IB, a special Xcode helper app loads the framework. Even if your IB-designable control is in your app target, Xcode is creating a special helper app to run the control inside of IB.

The solution? Call +[NSBundle bundleForClass:] instead (or NSBundle(forClass:) in Swift). This gets you the bundle containing the executable code for whichever class you specify. (You can use[self class]/self.dynamicType there, but beware the result will change for subclasses defined in different bundles.)

If you're using the framework approach — which can be useful for some apps even though it's no longer required for IB-designable controls — it's best to put image resources in the same framework with the code that uses them. If your framework code expects to use resources provided at run time by whatever app loads the framework, the best thing to do for making it IB-designable is to fake it. Implement the prepareForInterfaceBuilder method in your control and have it load resources from a known place (like the framework bundle or a static path in your Xcode workspace).

ref:stackoverflow.com/questions/2…


关于在block中使用ivar

For direct ivar access, use ->. For example:

__weak VeryCool *weakSelf = self;
something.changeHandler = ^(NSUInteger newIndex) {
    if (newIndex == 0) {
        VeryCool* strongSelf = weakSelf;
        if (strongSelf)
            [strongSelf->options removeObjectForKey:@"seller"];
    }
};

It's important to check that strongSelf is non-nil because direct access to an instance variable will crash for a nil pointer (which is different from invoking methods with a nil receiver and property access is just method invocation).

ref:stackoverflow.com/questions/1…

ivar也会造成retain cycle,所以需要使用上面的方法来,防止retain cycle

In a less obvious manifestation of the same potential retain cycle, any ivar you use will also capture the parent object:

// The following block will retain "self"
SomeBlockType someBlock = ^{
    BOOL isDone = _isDone;  // _isDone is an ivar of self
};

ref:ARC Best Practices

关于ARC中ivar

If a variable:

  1. Is declared in a class using ARC.

  2. Is used solely for class implementation (not exposed as part of the class interface). But U can access public ivar using classInstance->iVar = @"New value”, You should avoid this, it’s not a good programming practice.

  3. Does not require any KVO.

  4. Does not require any custom getter/setter. Then it is appropriate to declare it as an ivar without a corresponding @property/@synthesize, and to refer to it directly within the implementation. It is inline with Encapsulation to declare this ivar in the class implementation file.

    // MyClass.h @interface MyClass : ParentClass @end

    // MyClass.m @implementation MyClass { NSString *myString; }

    • (void)myMethod { myString = @"I'm setting my ivar directly"; } @end
  • This ivar will be treated as __strong by the ARC compiler.
  • It will be initialized to nil if it is an object, or 0 if it is a primitive. ref:stackoverflow.com/questions/7…

0.CAAnimation的组成

CAAnimation 是 CoreAnimation 基础的 Object, 而我們大多真正會使用的大該是下面五种继承CAAnimttion的物件
CAPropertyAnimation 主要使用 下面兩個subclass
               CAKeyframeAnimation
               CABasicAnimation
CAAnimationGroup
CATransition
CATransaction

关于CATransaction

最後來提提CATransaction; 在運用CALayer的時候, 只要我們一修改frame, Layer就會開始呈現動畫, 而CATransaction是可以用來控制這些動畫的時間跟動畫速率, 跟UIView的Aniamtion用法有點像.

[CATransaction begin];
if (!animated) {
    [CATransaction setValue:(id)kCFBooleanTrue
                     forKey:kCATransactionDisableActions];
}
else {
    [CATransaction setAnimationDuration:.3f];
}
self.layer.backgroundColor = [UIColor clearColor].CGColor;
self.gradientLayer.colors = colors;
self.gradientLayer.locations = locations;
[CATransaction commit];

1.关于coreimage图片渲染的一般流程.

749B22D9-A78C-40A8-B382-EE6EDC1E6C8D.png ref:www.cnblogs.com/YouXianMing…

2.解决CABasicAnimation resets to initial value after animation completes的问题

CATransform3D trans=CATransform3DIdentity;
trans.m34=-1.0/500.0;
trans=CATransform3DTranslate(trans, 10, -20, 0);
trans=CATransform3DRotate(trans, M_PI/5, 0.5, 1,0.75);
trans=CATransform3DScale(trans, 0.8, 0.8, 0.8);
// CABasicAnimation实现3D效果
CABasicAnimation *basicAnimation = [CABasicAnimation animationWithKeyPath:@"transform"];
basicAnimation.toValue = [NSValue valueWithCATransform3D:trans];
basicAnimation.removedOnCompletion = NO; // 动画结束后去除动画
basicAnimation.duration = 0.2f; // 设置动画时间
basicAnimation.cumulative = NO;
basicAnimation.repeatCount = 0; // 循环次数
basicAnimation.fillMode = kCAFillModeForwards; //这一句,就可以让动画结束时,保持最后的状态.
[_imgView.layer addAnimation:basicAnimation forKey:nil];

3.关键帧动画与基本动画类型没有什么区别,要说区别,那就是关键帧动画要比普通动画更强大,因为它能设置更多的值嘛,普通动画可以实现的效果都可以用关键帧动画替换哦.

8530786E-0814-4F13-B6AA-8C568B199473.png

4.关于图层的mask.

图层A被添加了遮罩层mask以后,那么除了被mask遮罩住的地方可以显示出来,其余的地方则不会显示,就像不存在一样. 但注意如果遮罩层mask的填充颜色为透明色的话,那么被遮罩的地方也不会显示出来. CALayer的默认backgroundColor是nil

5.UIBezierPath有个方法,可以直接在当前context中填充.

发注意一下drawRect调用的时候,是在布局完成之后,最后一步才是drawRect,当然也可以在合适的时机,手工调用 [self setNeedsDisplay];来强制drawRect

- (void)drawRect:(CGRect)rect{
    UIBezierPath  *round = [UIBezierPath bezierPathWithRoundedRect:CGRectMake((CGRectGetWidth(self.frame)-35)/2, CGRectGetHeight(self.frame)-12, 35, 5) byRoundingCorners:(UIRectCornerAllCorners) cornerRadii:CGSizeMake(10, 10)];
    [[UIColor lightGrayColor] setFill];
    [round fill];
}

上面是填充,同时,也还有一个stroke方法,能直接画线. 引用:开源库JKNotifier

6.在一个动画开始前,我们可以先移除正在运行的动画,然后再开始当前的动画

Remove all animations attached to the layer.

- (void)removeAllAnimations

    [self.notifierBar.layer removeAllAnimations];
    self.notifierBar.userInteractionEnabled = YES;
    [self.notifierBar show:note name:appName icon:appIcon];
    [UIView animateWithDuration:(0.4) animations:^{
        self.notifierBar.alpha = 1.0;
        self.notifierBar.transform = CGAffineTransformIdentity;
    }];

引用:开源库JKNotifier

7.CAShapeLayer支持的动画类型有如下这些.

/* The path defining the shape to be rendered. If the path extends

  • outside the layer bounds it will not automatically be clipped to the
  • layer, only if the normal layer masking rules cause that. Defaults
  • to null. Animatable. (Note that although the path property is
  • animatable, no implicit animation will be created when the property
  • is changed.) */ @property CGPathRef path;

/* The color to fill the path's stroked outline, or nil for no stroking.

  • Defaults to nil. Animatable. */ @property CGColorRef strokeColor;

/* These values define the subregion of the path used to draw the

  • stroked outline. The values must be in the range [0,1] with zero
  • representing the start of the path and one the end. Values in
  • between zero and one are interpolated linearly along the path
  • length. strokeStart defaults to zero and strokeEnd to one. Both are
  • animatable. */ @property CGFloat strokeStart, strokeEnd;

/* The line width used when stroking the path. Defaults to one.

  • Animatable. */ @property CGFloat lineWidth;

/* The miter limit used when stroking the path. Defaults to ten.

  • Animatable. */ @property CGFloat miterLimit;

/* The phase of the dashing pattern applied when creating the stroke.

  • Defaults to zero. Animatable. */ @property CGFloat lineDashPhase;

8. CATransformLayer和CALayer的区别.

// 普通的一个layer
CALayer *plane1        = [CALayer layer];
plane1.anchorPoint = CGPointMake(0.5, 0.5);                         // 锚点
plane1.frame       = (CGRect){CGPointZero, CGSizeMake(100, 100)};   // 尺寸          
plane1.opacity         = 0.6;  
    
// Z轴平移
CATransform3D plane1_3D = CATransform3DIdentity;
plane1_3D               = CATransform3DTranslate(plane1_3D, 0, 0, -10);
plane1.transform        = plane1_3D;
// 普通的一个layer
CALayer *plane2        = [CALayer layer];
plane2.anchorPoint = CGPointMake(0.5, 0.5);                         // 锚点
plane2.frame       = (CGRect){CGPointZero, CGSizeMake(100, 100)};   // 尺寸
plane2.opacity         = 0.6;   
// Z轴平移
CATransform3D plane2_3D = CATransform3DIdentity;
plane2_3D               = CATransform3DTranslate(plane2_3D, 0, 0, -30);
plane2.transform        = plane2_3D;
// 创建容器layer,注意区别在这里
// CALayer *container = [CALayer layer];
CATransformLayer *container = [CATransformLayer layer];
container.position=CGPointMake(0.5, 0.5);
container.frame    = self.view.bounds;
[self.view.layer addSublayer:container];
[container addSublayer:plane1];
[container addSublayer:plane2];
CATransform3D plane_3D = CATransform3DIdentity;
plane_3D.m34           = 1.0/ -500;
plane_3D               = CATransform3DRotate(plane_3D, DEGREE(30), 0, 1, 0);
container.transform    = plane_3D;

当使用CALayer作容器时,是这个样子,一个layer被另一个layer所隐藏了.

2A667C07-A0ED-434C-AF07-D797443D2DD6.png

当使用CATransformLayer作容器时,是这个样子,这样就有了3D效果.

57086AF5-3FEB-42F0-8996-E773A88E974A.png

ref: www.cnblogs.com/YouXianMing…

动画过程中有闪动问题的处理

Try using a CATransaction lock around your updates to see if that helps. This will prevent the previous animations from changing the position of the layers while you're in the process of updating them with new animations. In your touch handling method, wrap the animations in a transaction and lock:

[CATransaction lock];
[CATransaction begin];
// update the sublayers with new animations
[CATransaction commit];
[CATransaction unlock];

CAAnimationGroup会覆盖其动画数组中单个动画的属性

CAAnimationGroup *anim = [CAAnimationGroup animation];
[anim setAnimations:[NSArray arrayWithObjects:[self transparencyAnimation], [self translateAnimation], nil]];
[anim setDuration:3.0];//这个时间周期,会覆盖数组中其它动画的周期
[anim setRemovedOnCompletion:NO];//这个也会覆盖...
[anim setDelegate:self];//这个也会覆盖...
[anim setFillMode:kCAFillModeForwards];//这个也会
[layer addAnimation:anim forKey:nil];
- (CABasicAnimation *)transparencyAnimation {
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
    [animation setFromValue:[NSNumber numberWithFloat:1.0f]];
    [animation setToValue:[NSNumber numberWithFloat:0.2f]];
    return animation;
}
- (CABasicAnimation *)translateAnimation {
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
    [animation setFromValue:[NSValue valueWithCGPoint:CGPointMake(100.0, 100.0)]];
    animation.toValue = [NSValue valueWithCGPoint:CGPointMake(100.0, 250.0)];
    return animation;
}