UI-3

3

绘图

Quartz 2D

是一个二维绘图引擎,同时支持iOS和Mac。用于绘制各种图,并且能自定义View

  1. 图形上下文
    就是一个画板,是一个CGContextRef类型。图形上下文是用来保存用户绘制的内容状态,并决定绘制到哪个地方的.
    用户把绘制好的内容先保存到图形上下文,
    然后根据选择的图形上下文的不同,绘制的内容显示到地方也不相同,即输出目标也不相同.

  2. 什么是图形上下文,上下文的类型有哪些?

    图形上下文是用来保存用户绘制的内容状态,并决定绘制到哪个地方的.
    用户把绘制好的内容先保存到图形上下文,
    然后根据选择的图形上下文的不同,绘制的内容显示到地方也不相同,即输出目标也不相同.

        图形上下文的类型有:
        Bitmap Graphics Context(位图上下文)
        PDF Graphics Context
        Window Graphics Context 
        Layer Graphics Context(图层上下文,自定义UIView取得上下文就是图层上下文.
        UIView之所以能够显示就是因为他内部有一个图层)
        Printer Graphics Context
如何自定义UIView,步骤是什么?

首先得要有上下文,有了上下文才能决定把绘制的东西显示到哪个地方去.
其次就是这个上下文必须得和View相关联.才能将内容绘制到View上面.

步骤:
1.要先自定定UIView
2.实现DrawRect方法
3.在DrawRect方法中取得跟View相关联的上下文.
4.绘制路径(描述路径长什么样).
5.把描述好的路径保存到上下文(即:添加路径到上下文)
6.把上下文的内容渲染到View

drawRect

1.DrawRect方法作用?什么时候调用.
DrawRect作用:专用在这个方法当中绘图的.只有在这个方法当中才能取得跟View相关联的上下文.
DrawRect是系统自己调用的, 它是当View显示的时候自动调用.

2.画线(基本步骤描述)
1.获取跟View相关联的上下文.(UIGraphics都是以它开头)
CGContextRef ctx = UIGraphicsGetCurrentContext(); 
2.描述路径.
UIBezierPath *path = [UIBezierPath bezierPath]; 
2.1设置起点.
[path moveToPoint:CGPointMake(10, 150)]; 
2.2添加一根到某个点.
[path addLineToPoint:CGPointMake(200, 50)];
修改上下文的状态.
设置线的宽度
CGContextSetLineWidth(ctx, 10); 
设置线的连接样式.
CGContextSetLineJoin(ctx, kCGLineJoinBevel); 
设置线的顶角样式
CGContextSetLineCap(ctx, kCGLineCapRound); 
设置线的颜色.还可以直接用set这种方法
[[UIColor greenColor] set];    
3.要把路径添加到上下文当中.
 UIKit path -> CoreGraphics Path
CGContextAddPath(ctx, path.CGPath);
4.把上下文的内容渲染到View上. stroke(描边) fill(填充)
CGContextStrokePath(ctx);

3. 想要再添加一根线怎么办?
第一种方法:重新设置起点,添加一根线到某个点,一个UIBezierPath路径上面可以有多条线.
第二种方法:直接在原来的基础上添加线.把上一条的终点当做下一条线的起点.添加一根线到某个点
直接在下面addLineToPoint:CGPointMake(200, 50)

4.怎么样设置线的宽度,颜色,样式?
设置这些样式,我们称为是修改图形上下文的状态.
设置线宽:CGContextSetLineWidth(ctx, 10); 
设置线段的连接样式: CGContextSetLineJoin(ctx, kCGLineJoinBevel);
添加顶角样式:CGContextSetLineCap(ctx, kCGLineCapRound);
设置线的颜色: [[UIColor greenColor] set];  

5.如何画曲线?

画曲线方法比较特殊需要一个控制点来决定曲线的弯曲程度.画曲线方法为:
先设置一个曲线的起点
[path moveToPoint:CGPointMake(10, 150)]; 
再添加到个点到曲线的终点.同时还须要一个controlPoint(控件点决定曲线弯曲的方法程序)
[path addQuadCurveToPoint:CGPointMake(200, 150) controlPoint:CGPointMake(150, 10)]; 

6.如何画矩形,圆角矩形?

画矩形直接利用UIBezierPath给我们封装好的路径方法
(x,y)点决定了矩形左上角的点在哪个位置
(width,height)是矩形的宽度高度
bezierPathWithOvalInRect:CGRectMake(x, y, width, height)
[UIBezierPath bezierPathWithOvalInRect:CGRectMake(50, 50, 100, 100)];

圆角矩形的画法多了一个参数,cornerRadius
cornerRadius它是矩形的圆角半径.
通过圆角矩形可以画一个圆.当矩形是正方形的时候,把圆角半径设为宽度的一半,就是一个圆.
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(50, 50, 100, 100) cornerRadius:50]; 

7.如果画椭圆,圆

画椭圆的方法为:
前两个参数分别代码圆的圆心,后面两个参数分别代表圆的宽度,与高度.
宽高都相等时,画的是一个正圆, 不相等时画的是一个椭圆
[UIBezierPath bezierPathWithOvalInRect:CGRectMake(50, 50, 100, 100)];

8.如何利用UIKit封装的上下文进行画图?
直接来个: [path stroke];就可以了. 
它底层的实现,就是获取上下文,拼接路径,把路径添加到上下文,渲染到View

9.如何画圆弧?

首先要确定圆才能确定圆弧,圆孤它就圆上的一个角度嘛

Center:圆心
radius:圆的半径
startAngle:起始角度
endAngle:终点角度
clockwise:Yes顺时针,No逆时针

注意:startAngle角度的位置是从圆的最右侧为0度.
CGPoint center = CGPointMake(150, 150);
CGFloat radius = 100; 
CGFloat startA = 0;//圆的0度角在圆的最右侧, 
CGFloat endA = -M_PI_2; 
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startA endAngle:endA clockwise:NO];


10.如果画扇形.
画扇形的方法为:先画一个圆孤再添加一个一根线到圆心,然后关闭路径.
关闭路径就会自动从路径的终点到路径的起点封闭起下
用填充的话,它会默认做一个封闭路径,从路径的终点到起点.
[path fill]; 


绘制下载进度条(100%画圆)

- 如果drawRect是手动调用的,他是不会创建和View相关的上下文的,只有系统调用的时候才会创建。所以绘制完事要调用的话,就要重绘`setNeedsDisplay`
1.搭建界面.

2.拖动滑竿的时候让他里面的能够跟着我的拖动,数字在改变.
数字改变时有一个注意点, 就是要显示%,它是一个特殊的符号,要用两个%%代表一个%

3.拖动滑竿的时候就是在上面画弧.
从最上面,按顺时针画,所以,它的起始角度是-90度.结束角度也是-90度
也是从起始角度开始画,
起始角度-90度, 看你下载进度是多少
假如说你下载进度是100,就是1 * 360度
也就是说这个进度占你360度多少分之一

 1.获取跟View相关联的上下文.
 CGContextRef ctx = UIGraphicsGetCurrentContext(); 
 2.描述路径
 CGPoint center = CGPointMake(self.bounds.size.width * 0.5, self.bounds.size.height * 0.5); 
 CGFloat radius = self.bounds.size.width * 0.5 - 10; 
 CGFloat startA = -M_PI_2; 
 CGFloat endA = - M_PI_2 +  self.progress * M_PI * 2; 
 UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startA endAngle:endA clockwise:YES]; 
 3.把路径添加到上下文当中.
 CGContextAddPath(ctx, path.CGPath);
 4.把上下文的内容渲染到View上.
 CGContextStrokePath(ctx);


要获得Progress的值,这个进度值没有, 所以要传进来才能画.弄一个成员变量
要在值改变的时候就要传进来.
要拿到ProgressView才能够传进来,所以要拖线,拿到ProgressView
所有都做好的, 发现没有画圆孤?
为什么?
问题:drawRect方法总共调用多少次?
总共就调用一次, 第一次Progress为0,以后都不会执行了
解决:每次传的时候,就要画一次,
重写Progress方法
每一次传值的时候,重一次重绘,
-(void)setProgress:(CGFloat)progress{
   
    _progress = progress;
    [self drawRect:self.bounds];
    注意:当我们手动调用drawRect:方法时, 它并不会给我们创建跟VIEW相关联的上下文. 
    只有系统自动调用drawRect方法的时候才会创建跟View相关联的上下文. 
    当每次调用setNeedsDisplay这个方法,系统就会自动调用drawRect 
    [self setNeedsDisplay]; 
}
运行发现还是不画,为什么?
原因:drawRect方法是不能手动调用,因为在drawRect方法中才能获取跟View相关联的上下文
系统在调用DrawRect方法时,会自动帮你创建一个跟View相关联的上下文,并且传递给它.
自己调用的,没有给drawRect方法传递上下文.所以在draw方法中拿不到上下文.

解决办法:想要重绘,调用[self setNeedsDisplay];
告诉系统重新绘制View,系统就会自动帮你调用drawRect方法,系统在调用
drawRect方法,它会帮你创建上下文

UIKit绘图

图片,文字不用imageView,label是也可以实现的

一般使用UIKit给我们提供的绘图来绘制一些文字,图片这些东西.
UIKit给我们提供画图的方法底层也是分为四步.所以也必须在drawRect方法当中去写.



画文字
- (void)drawText{
    要绘制的文字 
    NSString *str = @"小码哥小码哥";
    NSMutableDictionary *dict = [NSMutableDictionary dictionary]; 
   
    设置字体 
    dict[NSFontAttributeName] = [UIFont systemFontOfSize:50]; 
    设置文字颜色 
    dict[NSForegroundColorAttributeName] = [UIColor redColor]; 
    设置文字描边宽度 
    dict[NSStrokeWidthAttributeName] = @2; 
    设置文字搭边的颜色 
    dict[NSStrokeColorAttributeName] = [UIColor blueColor]; 
   
    设置阴影 
    NSShadow *shadow = [[NSShadow alloc] init]; 
    设置阴影的偏移量 
    shadow.shadowOffset = CGSizeMake(10, 10); 
    设置阴影的颜色 
    shadow.shadowColor = [UIColor greenColor]; 
    设置阴影的模糊程度 
    shadow.shadowBlurRadius = 3;
    dict[NSShadowAttributeName] = shadow; 
   
    AtPoint:将文字画到哪一个点.
    描述文字的属性:文字大小,颜色...这些属性都是通过字典来描述的. 
    [str drawAtPoint:CGPointZero withAttributes:dict]; 
    [str drawInRect:self.bounds withAttributes:dict]; 
   
    drawInRect与drawAtPoint区别:
    drawInRect会自动换行,drawAtPoint不会自动换行.
}

- (void)drawImage{
    画图片 
    加载图片
    UIImage *image = [UIImage imageNamed:@"001"];
    画图片 
    在指定的点开始绘画图片,这个点就是图片的做上角顶点 
    绘制出来的图片跟加载的图片原始尺寸一样大.
    [image drawAtPoint:CGPointZero];
    会把加载的图片绘制到传入的rect区域内,填充整个区域. 
    [image drawInRect:rect];
    设置裁剪区域 
    超过裁剪区域以外的东西都会被裁剪掉 
    注意:这个方法必须要在绘制之前设置. 
    UIRectClip(CGRectMake(0, 0, 50, 50));
   
    平铺绘制 
    [image drawAsPatternInRect:self.bounds];
}



1.如何画文字?
先创建好要画的文字
使用UIKit提供的方法进行绘制.
方法说明:
drawAtPoint:要画到哪个位置
withAttributes:文本的样式.
[str drawAtPoint:CGPointZero withAttributes:nil];

2.如何添加绘制文字属性?
通过绘制方法的最后一个属性withAttributes来设置文字属性.
它要求传入的是一个字典.它是通过字典的key和Value的形式来设置文字样式.
那传什么key,什么值我们可以在UIKit头文件当中的NSAttributedString类当中去找.
使用形式如下:

创建一个可变的字典,设置key,value
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
字体
dict[NSFontAttributeName] = [UIFont systemFontOfSize:50];
颜色
dict[NSForegroundColorAttributeName] = [UIColor redColor];
设置边框颜色
dict[NSStrokeColorAttributeName] = [UIColor redColor];
dict[NSStrokeWidthAttributeName] = @1;
阴影
NSShadow *shadow = [[NSShadow alloc] init];
shadow.shadowOffset = CGSizeMake(10, 10);
shadow.shadowColor = [UIColor greenColor];
shadow.shadowBlurRadius = 3;
dict[NSShadowAttributeName] = shadow;

3.drawAtPoint:和drawInRect:的区别?
drawAtPoint:不能够自动换行
drawInRect:能够自动换行

4.如果绘制图片?
绘制图片同样开始要先把图片素材导入.
AtPoint:参数说明图片要绘制到哪个位置.
通过调用UIKit的方法drawAtPoint:CGPointZero方法进行绘制;

5.在绘制图片过程当中.drawAtPoint:和drawInRect:两个方法的区别?
drawAtPoint:绘制出来的图图片跟图片的实际尺寸一样大
drawInRect:使用这个方法绘制出来的图片尺寸会和传入的rect区域一样大.

6.如果进行平铺图片?
[image drawAsPatternInRect:rect];

7.如何选用UIKit提供的方法快速画一个矩形?
快速的用矩形去填充一个区域
UIRectFill(rect);

8.如何利用UIKit裁剪一个区域?
UIRectClip(CGRectMake(0, 0, 50, 50));
这个方法必须要设置好裁剪区域,才能有裁剪


字体字典


 字符属性
 
 字符属性可以应用于 attributed string 的文本中。
 
 NSString *const NSFontAttributeName;(字体)
 
 NSString *const NSParagraphStyleAttributeName;(段落)
 
 NSString *const NSForegroundColorAttributeName;(字体颜色)
 
 NSString *const NSBackgroundColorAttributeName;(字体背景色)
 
 NSString *const NSLigatureAttributeName;(连字符)
 
 NSString *const NSKernAttributeName;(字间距)
 
 NSString *const NSStrikethroughStyleAttributeName;(删除线)
 
 NSString *const NSUnderlineStyleAttributeName;(下划线)
 
 NSString *const NSStrokeColorAttributeName;(边线颜色)
 
 NSString *const NSStrokeWidthAttributeName;(边线宽度)
 
 NSString *const NSShadowAttributeName;(阴影)(横竖排版)
 
 NSString *const NSVerticalGlyphFormAttributeName;
 
 常量
 
 1> NSFontAttributeName(字体)
 
 该属性所对应的值是一个 UIFont 对象。该属性用于改变一段文本的字体。如果不指定该属性,则默认为12-point Helvetica(Neue)。
 
 2> NSParagraphStyleAttributeName(段落)
 
 该属性所对应的值是一个 NSParagraphStyle 对象。该属性在一段文本上应用多个属性。如果不指定该属性,则默认为 NSParagraphStyle 的defaultParagraphStyle 方法返回的默认段落属性。
 
 3> NSForegroundColorAttributeName(字体颜色)
 
 该属性所对应的值是一个 UIColor 对象。该属性用于指定一段文本的字体颜色。如果不指定该属性,则默认为黑色。
 
 4> NSBackgroundColorAttributeName(字体背景色)
 
 该属性所对应的值是一个 UIColor 对象。该属性用于指定一段文本的背景颜色。如果不指定该属性,则默认无背景色。
 
 5> NSLigatureAttributeName(连字符)
 
 该属性所对应的值是一个 NSNumber 对象(整数)。连体字符是指某些连在一起的字符,它们采用单个的图元符号。0 表示没有连体字符。1 表示使用默认的连体字符。2表示使用所有连体符号。默认值为 1(注意,iOS 不支持值为 2)。
 
 6> NSKernAttributeName(字间距)
 
 该属性所对应的值是一个 NSNumber 对象(整数)。字母紧排指定了用于调整字距的像素点数。字母紧排的效果依赖于字体。值为 0 表示不使用字母紧排。默认值为0。
 
 7> NSStrikethroughStyleAttributeName(删除线)
 
 该属性所对应的值是一个 NSNumber 对象(整数)。该值指定是否在文字上加上删除线,该值参考“Underline Style Attributes”。默认值是NSUnderlineStyleNone。
 
 8> NSUnderlineStyleAttributeName(下划线)
 
 该属性所对应的值是一个 NSNumber 对象(整数)。该值指定是否在文字上加上下划线,该值参考“Underline Style Attributes”。默认值是NSUnderlineStyleNone。
 
 9> NSStrokeColorAttributeName(边线颜色)
 
 该属性所对应的值是一个 UIColor 对象。如果该属性不指定(默认),则等同于 NSForegroundColorAttributeName。否则,指定为删除线或下划线颜色。更多细节见“Drawing attributedstrings that are both filled and stroked”。
 
 10> NSStrokeWidthAttributeName(边线宽度)
 
 该属性所对应的值是一个 NSNumber 对象(小数)。该值改变描边宽度(相对于字体size 的百分比)。默认为 0,即不改变。正数只改变描边宽度。负数同时改变文字的描边和填充宽度。例如,对于常见的空心字,这个值通常为3.0。
 
 11> NSShadowAttributeName(阴影)
 
 该属性所对应的值是一个 NSShadow 对象。默认为 nil。
 
 12> NSVerticalGlyphFormAttributeName(横竖排版)
 
 该属性所对应的值是一个 NSNumber 对象(整数)。0 表示横排文本。1 表示竖排文本。在 iOS 中,总是使用横排文本,0 以外的值都未定义。
 
 

CADisplayLink定时器完成雪花特效

1.定时器雪花整体思路:
先在控制器View面绘制一个雪花.
在View加载完毕后,添加一个定时器.
在定时器方法当中调用得绘方法.
在绘图方法当不段的去修改雪花的Y值.
当雪花的Y值超过屏幕的高度时,让雪花的Y值重新设为0.从最顶部开始.

2.添加定时器实现方案
第一种采用NSTime
第二种采用CADisplayLink
最终采用CADisplayLink方案.

2.1为什么采用CADisplayLink方案不用NSTime?

首先要了解setNeedsDisplay
setNeedsDisplay底层会调用DrawRect方法重绘.
但是它不是立马就进行重绘.它仅仅是设置了一个重绘标志,等到下一次屏幕刷新的时候才会调用DrawRect方法.

如果使用NSTime的话,假设是0.01调用一次重绘.假设屏幕0.02秒的时候它才刷新一次.中间就会等0.01秒.
也就是每次都会等0.01秒这样累加上去.让变的越来越卡顿.

使用CADisplayLink时,它的定时器方法就是屏幕每次刷新的时候就会调用(通常屏幕一秒钟刷新60次)
它和setNeedsDisplay调用DrawRect方法的时机正好吻合,不会出间等待间隔.不会出现屏幕卡顿现象.

2.2如何使用CADisplayLink添加定时器?
Target:哪个对象要监听方法.
selector:监听的方法名称.
CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self
selector:@selector(setNeedsDisplay)];
想要让CADisplayLink工作,必须得要把它添加到主运行循环.
只要添加到主运行循环, 跟模式没有关系
[link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];

3.具体实现代码如下:
-(void)awakeFromNib{

  添加定时器 
  [NSTimer scheduledTimerWithTimeInterval:0.025 target:self selector:@selector(update) userInfo:nil repeats:YES];
   
  如果我们使用NSTimer定时器. 设置的执行时间为0.025秒, 
  假如屏幕刷新时间为0.035.中间就会等待0.010;
   
  我们在绘制的时候使用定时器最好使用CADisplayLink. 
  创建CADisplayLink定时器 
  这个定时器方法它是当每次屏幕刷新的时候调用(屏幕每一秒刷新60次); 

  使用CADisplayLink不需要考虑时间间隔. 
  CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(update)]; 
  要让它工作, 必须得要把定时器添加到主运行循环 
  [link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; 
  
}
static int _snowY = 0; 
定时器方法
- (void)update{
   修改绘制雪花的Y值. 
   _snowY  +=10; 
   当_snowY大于屏幕的高度时, 从0开始 
    if(_snowY > self.bounds.size.height){
        _snowY = 0; 
    }
    修改完毕做一次重绘 
    setNeedsDisplay:这个方法底层会调用drawRect方法, 但是它并不是立马调用的, 它只是设了一个标志.它会等到下一次屏幕刷新的时候再去调用drawRect. 
    [self setNeedsDisplay];
} 

- (void)drawRect:(CGRect)rect {
    添加一个定时器, 每次绘制修改雪花的Y值. 
    把雪花图片绘制到View 
    UIImage *image = [UIImage imageNamed:@"雪花"];
    [image drawAtPoint:CGPointMake(0, _snowY)];
} 

图形上下文状态

上下文状态栈为内存中的一块区域,它用来保存前上下文当的状态.
我们获取的图层上下文当中其实两块区域,一个是存放添加的路径,一个是用来保存用户设置的状态,
这些状态包括线条的颜色,线宽等.
当我们把上下文的内容渲染到View上面的时候,
它会自动将设置的所有上下文状态运行到保存的路径上面显示到View上面.

如果想要有多种状态,可以先把路径渲染到View上面,
再从新添加路径.添加完路径之后,重新设置上下文的状态.
再把新设置的上下文状态渲染到View上面.

我们可以利用上下文状态栈的方式,在设置状态之前,把之前的状态保存到上下文状态栈里面.
下一次想要再使用之前的状态时, 可以从上下文状态当中取出之前保存的上下文状态.

1.如何把上下文状态保存到上下文状态栈?
CGContextSaveGState(ctx);
2.如何从上下文状态栈中取出上下文状态?
CGContextRestoreGState(ctx);

- (void)drawRect:(CGRect)rect {
    1.获取跟View相关联的上下文.
   CGContextRef ctx =  UIGraphicsGetCurrentContext();
    2.描述路径
    UIBezierPath *path = [UIBezierPath bezierPath];
    设置起点 
    [path moveToPoint:CGPointMake(10, 150)];
    添加一条线到某个点 
    [path addLineToPoint:CGPointMake(290, 150)];
    将当前的上下文保存到上下文状态栈当中. 
    CGContextSaveGState(ctx);
    设置上下文的状态 
    CGContextSetLineWidth(ctx, 10);
    [[UIColor redColor] set];
    3.把路径添加到上下文当中.
    CGContextAddPath(ctx, path.CGPath);
    4.把上下文的内容渲染到View
    CGContextStrokePath(ctx);
   
    先把一条路径渲染到View. 
    再添加另一条路径,重新设置上下文的状态,再把路径渲染到View. 
   
    path = [UIBezierPath bezierPath]; 设置起点
    [path moveToPoint:CGPointMake(150, 10)];
    添加一条线到某个点 
    [path addLineToPoint:CGPointMake(150, 290)];
    从上下文状态栈中取出一个状态. 
    CGContextRestoreGState(ctx);
    重新设置上下文的状态. 
    3.把路径添加到上下文当中. 
    CGContextAddPath(ctx, path.CGPath);
    4.把上下文的内容渲染到View
    CGContextStrokePath(ctx);
   
   
    上下文状态栈. 
    用来保存当前上下文当中的状态. 
    使用CGContextSaveGState就会将当前的上下文保存到上下文状态栈中.
    使用GContextRestoreGState取出一个状态. 

图形上下文矩阵操作

绘图的时候也可以做平移,缩放,旋转.
这个方法可以写成分类,快速根据Image创建一张水印Image

- (void)drawRect:(CGRect)rect {
   
    获取当前的上下文. 
    CGContextRef ctx = UIGraphicsGetCurrentContext(); 
    画一个椭圆 
    UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(-100, -50, 200, 100)]; 
    上下文的矩阵操作,就是可以上下文当中的路径进行一些形变操作. 
    平移操作 
    CGContextTranslateCTM(ctx, 100, 50); 
    缩放操作 
    CGContextScaleCTM(ctx, 0.5, 0.5); 
   
    旋转操作 
    CGContextRotateCTM(ctx, M_PI_4); 
   
    注意形变操作要在添加路径之前进行. 
    [[UIColor redColor]set]; 
    把路径添加到上下文. 
    CGContextAddPath(ctx, path.CGPath); 
    把上下文的内容渲染到View. 
    CGContextFillPath(ctx);   
}

图片加水印

给图片水印的目的:
告诉别人图片的来源.
防止别人盗用图片.打广告.

添加水印它最终是生成了一个新的图片.
生成图片要用到了图片上下文.不需要再去自定义View,
之前一直在自定义View,是因为要拿跟View相关联的上下文.
跟View相关联的上下文是系统自动帮我们创建的,所以不需要我们自己手动创建,
但是图片上下文需要我们自己去手动创建.还需要我们自己手动去关闭.

实现水印效果的思路:
开启一个和原始图片一样的图片上下文.
把原始图片先绘制到图片上下文.
再把要添加的水印(文字,logo)等绘制到图片上下文.
最后从上下文中取出一张图片.
关闭图片上下文.


    加载要添加水印的图片 
    UIImage *image = [UIImage imageNamed:@"小黄人"]; 
   
    创建一个位图上下文. 
    size:要开启一个多大的图片上下文. 
    opaque:不透明度,当为YES为不透明, 为NO的时候透明, 
    scale:是否需要缩放. 
    UIGraphicsBeginImageContextWithOptions(image.size, YES, 0); 
   
    把要添加水印的图片绘制到上下文当中. 
    [image drawAtPoint:CGPointZero];
    [image drawAtPoint:CGPointZero blendMode:kCGBlendModeNormal alpha:1]; 
   
    绘制水印 
    NSString *str = @"小码哥"; 
    [str drawAtPoint:CGPointMake(20, 400) withAttributes:
     @{NSFontAttributeName:[UIFont systemFontOfSize:50],
       NSForegroundColorAttributeName : [UIColor colorWithRed:255/255.0 green:0 blue:0 alpha:0.3] 
    }]; 
    从上下文当中生成一张新图片. 
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); 

    关闭上下文. 
    UIGraphicsEndImageContext(); 
    self.imageV.image = newImage;


简单的圆形图片裁剪

裁剪图片思路.
开启一个图片上下文.
上下文的大小和原始图片保持一样.以免图片被拉伸缩放.
在上下文的上面添加一个圆形裁剪区域.圆形裁剪区域的半径大小和图片的宽度一样大.
把要裁剪的图片绘制到图片上下文当中.
从上下文当中取出图片.
关闭上下文.

    加载要裁剪的图片
    UIImage *image = [UIImage imageNamed:@"阿狸头像"]; 
    1.创建一个和原始图片一样大小的位图上下文.
    UIGraphicsBeginImageContextWithOptions(image.size, NO, 0); 
    2.设置一个圆形的裁剪区域
    UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect: 
                          CGRectMake(0, 0, image.size.width, image.size.height)]; 
    把路径设置成裁剪区域 
    [path addClip]; 
    3.把原始图片绘制上下文当中.
    [image drawAtPoint:CGPointZero]; 
    4.从上下文当中生成一张新的图片.
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); 
    5.关闭上下文.
    UIGraphicsEndImageContext();

屏幕截屏

点击屏幕时,把控件的View截屏
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ 

    把View的内容截屏生成一张新的图片. 
    开启一个跟控制器view相同大小的上下文. 
    UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, NO, 0); 

    把View的内容绘制到上下文当中. 
    注意:View是不能够直接绘制到上下文当中的.View之所以能够显示是因为它内部有一个layer(层), 
    层是通过渲染的方式绘制到上下文当中的. 
   
    获取当前的上下文. 
    CGContextRef ctx = UIGraphicsGetCurrentContext(); 
    把当前View的内容渲染到View上面.
    [self.view.layer renderInContext:ctx]; 
   
    从上下文当中生成一张图片 
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); 
    关闭上下文. 
    UIGraphicsEndImageContext(); 
   
    把生成的图片写到桌面上. 
    桌面都是以流的形式传递数据,所以我们要把图片转成二进流. 
    image:要转的图片 
    compressionQuality:压缩质量,1代表质量最高 
    NSData *data = UIImageJPEGRepresentation(newImage, 1); 
    原始质量的png图片. 
    NSData *data = UIImagePNGRepresentation(newImage); 
    把二进流写到桌面.
    [data writeToFile:@"/Users/gaoxinqiang/Desktop/newImage.png" atomically:YES]; 
}


图片截屏(美图截图效果)

图片截屏实现思路.
手指在屏幕上移动的时
添加一个半透明的UIView,
然后开启一个上下文把UIView的frame设置成裁剪区域.把图片显示的图片绘制到上下文当中,生成一张新的图片
再把生成的图片再赋值给原来的UImageView.

具体实现步骤:
1.给图片添加一个手势,监听手指在图片上的拖动,添加手势时要注意,UIImageView默认是不接事件的.
要把它设置成能够接收事件
2.监听手指的移动.手指移动的时候添加一个UIView,
x,y就是起始点,也就是当前手指开始的点.
width即是x轴的偏移量,
高度即是Y轴的偏移量.
UIView的尺寸位置为CGrect(x,y,witdth,height);

计算代码为:
CGFloat offSetX = curP.x - self.beginP.x;
CGFloat offsetY = curP.y - self.beginP.y;
CGRect rect = CGRectMake(self.beginP.x, self.beginP.y, offSetX, offsetY);

UIView之需要添加一次,所以给UIView设置成懒加载的形式,
保证之有一个.每次移动的时候,只是修改UIView的frame.

3.开启一个图片上下文,图片上下文的大小为原始图片的尺寸大小.使得整个屏幕都能够截屏.
利用UIBezierPath设置一个矩形的裁剪区域.
然后把这个路径设置为裁剪区域.
把路径设为裁剪区域的方法为:
[path addClip];

4.把图片绘制到图片上下文当中
由于是一个UIImageView上面的图片,所以也得需要渲染到上下文当中.
要先获取当前的上下文,
把UIImageView的layer渲染到当前的上下文当中.
CGContextRef ctx = UIGraphicsGetCurrentContext();
[self.imageV.layer renderInContext:ctx];

5.取出新的图片,重新赋值图片.
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
self.imageV.image = newImage;

6.关闭上下文,移除上面半透明的UIView
UIGraphicsEndImageContext();
[self.coverView removeFromSuperview];


实现代码:
@interface ViewController () 
图片
@property (weak, nonatomic) IBOutlet UIImageView *imageV; 
起始点
@property(nonatomic,assign) CGPoint startP; 
遮盖
@property(nonatomic,weak)UIView *coverView;
@end

@implementation ViewController 

懒加载遮盖,保存遮盖只有一份.
-(UIView *)coverView{
    if (_coverView == nil) {
        创建遮盖
        UIView *cover = [[UIView alloc] init]; 
        cover.backgroundColor = [UIColor blackColor];
        设置透明度
        cover.alpha = 0.7;
        [self.view addSubview:cover];
        _coverView = cover;
    }
    return _coverView; 
}


- (IBAction)pan:(UIPanGestureRecognizer *)pan{
    手指移动时添加一个透明的遮盖.遮盖的大小根据手指移动的大小来确定. 
    当手指开时.把遮盖的范围当做是一个裁剪区域. 
    把图片绘制到上下文中.超过裁剪范围的图片会被裁剪掉.
    重新生成一张新的图片.给原来的UIImageView赋值. 
   
    CGPoint curP = [pan locationInView:self.imageV];
    if (pan.state == UIGestureRecognizerStateBegan) {
        获取手指当前点.从哪个点开始. 
        CGPoint startP = curP;
        self.startP = startP;

    }else if(pan.state == UIGestureRecognizerStateChanged){
        遮盖的大小根据手指移动的大小来确定.
        x轴的偏移量
        CGFloat offsetX = curP.x - self.startP.x;
        y轴的偏移量
        CGFloat offsetY = curP.y - self.startP.y; 
        设置遮盖的Frame
        CGRect rect = CGRectMake(self.startP.x, self.startP.y, offsetX, offsetY);
        self.coverView.frame = rect;
       
    }else if(pan.state == UIGestureRecognizerStateEnded){
        当手指开时.把遮盖的范围当做是一个裁剪区域. 
        把图片绘制到上下文中.超过裁剪范围的图片会被裁剪掉. 
        重新生成一张新的图片.给原来的UIImageView赋值. 
       
        创建一个和当前ImageView相同大小的位图上下文. 
        UIGraphicsBeginImageContextWithOptions(self.imageV.bounds.size, NO, 0);
        设置裁剪区域 
        UIBezierPath *path = [UIBezierPath bezierPathWithRect:self.coverView.frame];
        把矩形路径设置成裁剪区域
        [path addClip];
        把图片绘制到上下文当中.超过裁剪区域的部分会自动裁剪掉 
        当前是UIImageView,它也是UIView.所以也必须得要用渲染的方式绘制 
       
        获取当前的上下文 
         CGContextRef ctx =  UIGraphicsGetCurrentContext();
        [self.imageV.layer renderInContext:ctx];
        从上下文当中生成一张图片 
        UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
        关闭位图上下文. 
        UIGraphicsEndImageContext();
        给原来的UIImageView重新赋值. 
        self.imageV.image = newImage;
        把遮盖移除 
        [self.coverView removeFromSuperview];
       
       
   
    }


图片擦除

图片擦除思路.
弄两个不同的图片.上面一张, 下面一张.
添加手势,手指在上面移动,擦除图片.
擦除前要先确定好擦除区域.
假设擦除区域的宽高分别为30.
那点当前的擦除范围应该是通过当前的手指所在的点来确定擦除的范围,位置.
那么当前擦除区域的x应该是等于当前手指的x减去擦除范围的一半,同样,y也是当前手指的y减去高度的一半.

有了擦除区域,要让图片办到擦除的效果,首先要把图片绘制到图片上下文当中, 在图片上下文当中进行擦除.
之后再生成一张新的图片,把新生成的这一张图片设置为上部的图片.那么就可以通过透明的效果,看到下部的图片了.

第一个参数, 要擦除哪一个上下文
第二人参数,要擦除的区域.
CGContextClearRect(ctx, rect);

- (void)viewDidLoad {
    [super viewDidLoad];   
    self.imageV.userInteractionEnabled = YES;
    添加手势 
    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
    [self.imageV addGestureRecognizer:pan ];
}

当手指在屏幕上拖动的时候调用.
- (void)pan:(UIPanGestureRecognizer *)pan{
    获取当前的手指点. 
    CGPoint curP = [pan locationInView:self.imageV];
    确定擦除区域. 
    
        假设擦除区域的宽高分别为30.
        那点当前的擦除范围应该是通过当前的手指所在的点来确定擦除的范围,位置.
        那么当前擦除区域的x应该是等于当前手指的x减去擦除范围的一半,同样,y也是当前手指的y减去高度的一半.
    CGFloat rectWH = 30;
    CGFloat x = curP.x - rectWH * 0.5;
    CGFloat y = curP.y - rectWH * 0.5;
    确实擦除的位置尺寸. 
    CGRect rect = CGRectMake(x, y, rectWH, rectWH);
    开启一个位图上下文. 
    UIGraphicsBeginImageContextWithOptions(self.imageV.bounds.size, NO, 0);
    获取当前的上下文. 
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    把UIImageV上的图片渲染到上下文当中. 
    [self.imageV.layer renderInContext: ctx];
    设置擦除区域 
    CGContextClearRect(ctx, rect);
    生成一张新的图片. 
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    关闭位图上下文. 
    UIGraphicsEndImageContext();
    给图片重新赋值 
    self.imageV.image = newImage;
}


手势解锁

1.手指在按钮范围内,按钮是选中状态。

/**
 *  获取当前手指所在的点
 *
 *  @param touches touches集合
 *
 *  @return 当前手指所在的点.
 */ 
- (CGPoint)getCurrentPoint:(NSSet *)touches{
    UITouch *touch = [touches anyObject];
    return [touch locationInView:self];
} 

/**
 *  判断一个点在不在按钮上.
 *
 *  @param point 当前点
 *
 *  @return 如果在按钮上, 返回当前按钮, 如果不在返回nil.
 */ 
- (UIButton *)btnRectContainsPoint:(CGPoint)point{
     遍历所有的子控件
    for (UIButton *btn in self.subviews) { 
         判断手指当前点在不在按钮上.
        if (CGRectContainsPoint(btn.frame, point)) { 
            在按钮上.返回当前按钮 
            return btn;
        }
    }
    return nil;
   
} 

手指点击时让按钮成选中状态
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ 

    判断当前手指在不在按钮上,如果在按钮上, 让按钮成为选中状态.   
    1.获取当前手指所在的点 
    CGPoint curP = [self getCurrentPoint:touches]; 
    2.判断当前手指所在的点在不在按钮上.
    UIButton *btn  = [self btnRectContainsPoint:curP];
    if (btn) {
        btn.selected = YES; 
    }
}

手指移动时,按钮选中,连线到当前选中的按钮
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{ 

    判断当前手指在不在按钮上,如果在按钮上, 让按钮成为选中状态. 
    1.获取当前手指所在的点 
    CGPoint curP = [self getCurrentPoint:touches]; 
    2.判断当前手指所在的点在不在按钮上. 
    UIButton *btn  = [self btnRectContainsPoint:curP];
    if (btn) {
        btn.selected = YES; 
    }
}

2.连线


/**
 *  选中的按钮数组.
 *  每次选中一个按钮时,都把按钮添加到数组当中.移动添加到按钮当中时做一次重绘.
 *  重绘过程中取出所有保存的按钮, 判断是不是第一个按钮, 如果是第一个按钮,那就让它成为路径的起点.
 *  如果不是第一个按钮,那就添加一根线到按钮的中心点.
 */
@property(nonatomic,strong)NSMutableArray *selectBtn; 

@end

懒加载数组.
-(NSMutableArray *)selectBtn{  
    if (_selectBtn == nil) {
        _selectBtn = [NSMutableArray array];
    }
    return _selectBtn;
}

手指点击时让按钮成选中状态
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ 

    判断当前手指在不在按钮上,如果在按钮上, 让按钮成为选中状态. 
    1.获取当前手指所在的点 
    CGPoint curP = [self getCurrentPoint:touches]; 
   
    2.判断当前手指所在的点在不在按钮上.
   UIButton *btn  = [self btnRectContainsPoint:curP]; 
   if (btn && btn.selected == NO) {如果按钮已经是选中状态,就不让它再添加到数组当中 
        让按钮成为选中状态 
        btn.selected = YES; 
        把选中按钮添加到数组当中 
        [self.selectBtn addObject:btn];
    }
} 

手指移动时,按钮选中,连线到当前选中的按钮
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{ 
    判断当前手指在不在按钮上,如果在按钮上, 让按钮成为选中状态. 
    1.获取当前手指所在的点
    CGPoint curP = [self getCurrentPoint:touches]; 
    2.判断当前手指所在的点在不在按钮上. 
    UIButton *btn  = [self btnRectContainsPoint:curP]; 
    if (btn && btn.selected == NO) {//如果按钮已经是选中状态,就不让它再添加到数组当中
        让按钮成为选中状态 
        btn.selected = YES; 
        把选中按钮添加到数组当中 
        [self.selectBtn addObject:btn]; 
        每次添加完毕后做一次重绘. 
        [self setNeedsDisplay]; 
    }
}

- (void)drawRect:(CGRect)rect {
    创建路径. 
    UIBezierPath *path = [UIBezierPath bezierPath];
    取出所有保存的选中按钮连线. 
    for(int i = 0; i < self.selectBtn.count;i++){
        UIButton *btn = self.selectBtn[i];
        判断当前按钮是不是第一个,如果是第一个,把它的中心设置为路径的起点. 
        if(i == 0){
            设置起点. 
            [path moveToPoint:btn.center];
        }else{
            添加一根线到当前按钮的圆心. 
            [path addLineToPoint:btn.center];
        }
    }
   
    设置颜色 
    [[UIColor redColor] set];
    设置线宽 
    [path setLineWidth:10];
    设置线的连接样式 
    [path setLineJoinStyle:kCGLineJoinRound];
    绘制路径. 
    [path stroke];
}


核心动画

CALayer

1.CALayer简介:
CALayer我们又称它叫做层.
在每个UIView内部都有一个layer这样一个属性.
UIView之所以能够显示,就是因为它里面有这个一个层,才具有显示的功能.
我们通过操作CALayer对象,可以很方便地调整UIView的一些外观属性.
可以给UIView设置阴影,圆角,边框等等...

2.操作layer改变UIView外观.

2.1.设置阴影
默认图层是有阴影的, 只不过,是透明的
_RedView.layer.shadowOpacity = 1;
设置阴影的圆角
_RedView.layer.shadowRadius =10;
设置阴影的颜色,把UIKit转换成CoreGraphics框架,用.CG开头
_RedView.layer.shadowColor = [UIColor blueColor].CGColor;

2.2.设置边框
设置图层边框,在图层中使用CoreGraphics的CGColorRef
_RedView.layer.borderColor = [UIColor whiteColor].CGColor;
_RedView.layer.borderWidth = 2;

2.3.设置圆角
图层的圆角半径,圆角半径为宽度的一半, 就是一个圆
_RedView.layer.cornerRadius = 50;

3.操作layer改变UIImageView的外观.

设置图形边框
_imageView.layer.borderWidth = 2;
_imageView.layer.borderColor = [UIColor whiteColor].CGColor;


设置图片的圆角半径
_imageView.layer.cornerRadius = 50;
裁剪,超出裁剪区域的部分全部裁剪掉
_imageView.layer.masksToBounds = YES;
注意:UIImageView当中Image并不是直接添加在层上面的.这是添加在layer当中的contents里.
我们设置层的所有属性它只作用在层上面.对contents里面的东西并不起作用.所以我们看不到图片有圆角的效果.
想要让图片有圆角的效果.可以把masksToBounds这个属性设为YES,
当设为YES,把就会把超过根层以外的东西都给裁剪掉.


两个重要的属性:postion就是父控件基于的位置,然后看anchorPoint选择到底是View的哪个位置放到这个位置。
- postiton:以父控件左上角为原点
- anchorPoint:以自己左上角为原点,xy取值范围是0~1,默认是0.5,0.5

position和anchorPoint是CAlayer的两个属性.
我们以前修改一个控件的位置都是能过Frame的方式进行修改.
现在利用CALayer的position和anchorPoint属性也能够修改控件的位置.
这两个属性是配合使用的.
position:它是用来设置当前的layer在父控件当中的位置的.
所以它的坐标原点.以父控件的左上角为(0.0)点.

anchorPoint:它是决点CALayer身上哪一个点会在position属性所指的位置
anchorPoint它是以当前的layer左上角为原点(0.0)
它的取值范围是0~1,它的默认在中间也就是(0.5,0.5)的位置.
anchorPoint又称锚点.就是把锚点定到position所指的位置.

两者结合使用.想要修改某个控件的位置,我们可以设置它的position点.
设置完毕后.layer身上的anchorPoint会自动定到position所在的位置

CATransform3D

形变肯定是transform属性,layer的transform属性就是CATransform属性,参数比View的transform要多,是一个矩阵的类型


只有旋转的时候才可以看出3D的效果.

旋转
x,y,z 分别代表x,y,z轴.
CATransform3DMakeRotation(M_PI, 1, 0, 0);
平移
CATransform3DMakeTranslation(x,y,z)
缩放
CATransform3DMakeScale(x,y,z);

自定义layer

1.如何自定义Layer.
自定义CALayer的方式创建UIView的方式非常相似.
CALayer *layer = [CALayer layer];
layer.frame = CGRectMake(50, 50, 100, 100);
layer.backgroundColor = [UIColor redColor].CGColor;
[self.view.layer addSublayer:layer];

给layer设置图片.
layer.contents = (id)[UIImage imageNamed:@"阿狸头像"].CGImage;

2.关于CALayer的疑惑?
为什么要使用CGImageRef、CGColorRef?
为了保证可移植性,QuartzCore不能使用UIImage、UIColor,只能使用CGImageRef、CGColorRef

UIView和CALayer都能够显示东西,该怎样选择?

对比CALayer,UIView多了一个事件处理的功能。也就是说,CALayer不能处理用户的触摸事件,而UIView可以
如果显示出来的东西需要跟用户进行交互的话,用UIView;
如果不需要跟用户进行交互,用UIView或者CALayer都可以
CALayer的性能会高一些,因为它少了事件处理的功能,更加轻量级

隐式动画

什么是隐式动画?
了解什么是隐式动画前,要先了解是什么根层和非根层.

根层:UIView内部自动关联着的那个layer我们称它是根层.
非根层:自己手动创建的层,称为非根层.

隐式动画就是当对非根层的部分属性进行修改时, 它会自动的产生一些动画的效果.
我们称这个默认产生的动画为隐式动画.

如何取消隐式动画?
首先要了解动画底层是怎么做的.动画的底层是包装成一个事务来进行的.

什么是事务?
很多操作绑定在一起,当这些操作执行完毕后,才去执行下一个操作.

开启事务
[CATransaction begin];
设置事务没有动画
[CATransaction setDisableActions:YES];
设置动画执行的时长
[CATransaction setAnimationDuration:2];
提交事务
[CATransaction commit];

Core Animation

  • 面试也会问到

核心动画直接作用于CALayer,是一组非常强大的动画处理API,动画执行过程都是在后台完成的,不会阻塞主线程。乔帮主2007年演示这个,开800个视频在MAC上不卡,就是这门技术,同时它也是跨平台的。

核心动画之作用在层上面.
动画的本质是改图层的某一个属性.
CABasicAnimation *anim = [CABasicAnimation animation];
图层有那些属性,这里才能写那些属性.
anim.keyPath = @"transform.scale";
anim.toValue = @0.5;
告诉动画完成的时候不要移除
anim.removedOnCompletion = NO;
保存动画最前面的效果.
anim.fillMode = kCAFillModeForwards;
把动画添加到层上面.
[_redView.layer addAnimation:anim forKey:nil];

心跳动画(CABasicAnimation)

思路:就是让一张图片做一个放大缩放小的动画.

     CABasicAnimation *anim =[CABasicAnimation  animation];
     想让哪一个图层做动画,就添加到哪一个图层上面.
     anim.keyPath = @"transform.scale";
     anim.toValue = @0;
     [self.heartView.layer addAnimation:anim forKey:nil];
   
    运行发现, 一点它会缩放一下, 是不是想让它还原,那怎么做.
    想让它一直跳一直跳. 
    
    可以设置一下动画的执行次数.
    anim.repeatCount = MAXFLOAT;
   
    运行一下看一下效果.
    是不是有心跳的效果了, 只不过跳的非常快嘛.
    它默认是绕着哪个点缩放的.是不是锚点缩放啊.
 
    这样是不是执行的非常快, 我一点它马上就还原了.
    可以设置一个动画的时长.
    anim.duration = 1;
 
    运行后是不是跳的变慢了啊, 那还有一个问题哦, 是不是一下子就闪过来了啊 
    是不是回的太快了吧.如果想让它慢慢的回.我们有一个属性.
    anim.autoreverses = YES;

  CABasicAnimation *anim =[CABasicAnimation  animation]; 
  想让哪一个图层做动画,就添加到哪一个图层上面. 
  anim.keyPath = @"transform.scale"; 
  缩放到最小
  anim.toValue = @0; 
  设置动画执行的次数
  anim.repeatCount = MAXFLOAT;
  设置动画执行的时长
  anim.duration = 0.25; 
  反转
  anim.autoreverses = YES; 
  添加动画
  [self.heartView.layer addAnimation:anim forKey:nil];

图片抖动效果(CAKeyFrameAnimation)

1.帧动画介绍:
CAKeyframeAnimation它可以在多个值之间进行动画.
设置多值之间的属性为:
后面是一个数组,就是要设置的多个值.
anim.values = @[];

它还可以根据一个路径做动画.
anim.path = 自己创建的路径.

2.图片抖动思路:
其实就是做一个左右旋转的动画.先让它往左边旋转-5,再往右边旋转5度,再从5度旋转到-5度.
就会有左右摇摆的效果了.

具体实现代码
创建帧动画
CAKeyframeAnimation *anim = [CAKeyframeAnimation animation];
设置动画属性为旋转
anim.keyPath = @"transform.rotation";
设置属性值为多个属性
anim.values = @[@(angle2radio(-5)),@(angle2radio(5)),@(angle2radio(-5))];
设置动画执行次数
anim.repeatCount = MAXFLOAT;
添加动画
[_imageView.layer addAnimation:anim forKey:nil];

3.根据圆形的路径做移动的效果.
创建路径
UIBezierPath *path = [UIBezierPath
bezierPathWithOvalInRect:CGRectMake(50, 50, 100, 100)];
[path addLineToPoint:CGPointMake(200, 500)];

把路径设为动画的属性
anim.path = path.CGPath;

转场动画(CATransition)

例如imageView动态的切换图片
例如view动态的切换内容

type类型有很多。fade渐变,push推出,cube立体翻滚,还有相机效果,相上下翻滚效果等等

1.什么是转场动画?
就是从一个场景转换到另一个场景,像导航控制器的push效果,就是一个转场.

2.如何创建转场动画
创建转场动画
CATransition *anim = [CATransition animation];
设置转场类型
anim.type = @"cube";
anim.duration = 1;
设置转场的方向
anim.subtype = kCATransitionFromLeft;
设置动画的开始位置
anim.startProgress = 0.5;
设置动画的结束位置
anim.endProgress =0.8;
添加动画.了
[_imageV.layer addAnimation:anim forKey:nil];


要执行动画的代码称为转场代码.
转场动画要和转场代码写在同一个方法当中才有动画效果.

3.UIView进行转场动画

[UIView transitionWithView:self.imageV duration:1
options:UIViewAnimationOptionTransitionFlipFromRight
animations:^{
转场代码
} completion:^(BOOL finished) {
动画执行完毕时调用.
}];

使用UIView转场的类型比较少.

动画组(CAAnimationGroup)

可以同时执行多组动画

可以同时执行多个动画.
创建组动画
CAAnimationGroup *group = [CAAnimationGroup animation];

平移
CABasicAnimation *anim = [CABasicAnimation animation];
anim.keyPath = @"position.y";
anim.toValue = @400;

缩放
CABasicAnimation *scaleAnim = [CABasicAnimation animation];
scaleAnim.keyPath = @"transform.scale";
scaleAnim.toValue = @0.5;
设置动画组属性
group.animations = @[anim,scaleAnim];

group.removedOnCompletion = NO;
group.fillMode = kCAFillModeForwards;
添加组动画
[self.redView.layer addAnimation:group forKey:nil];

使用动画组的好处,不需要每次都去添加动画,设置动画完成时的属性.
只需要把要执行的动画,添加到动画组的animations数组当中即可,
最后把组动画添加到层上面,就会自动执行数组当中的动画.
动画完成时设置的属性也只需要设置一次.

(****)UIView动画和核心动画区别

1.UIView([UIView animation])和核心动画区别(layer)?
核心动画只能添加到CALayer
核心动画一切都是假象,并不会改变真实的值。(交互的时候就杯具了)

2.什么时候使用UIView的动画?
如果需要与用户交互就使用UIView的动画.
不需要与用户交互可以使用核心动画

3.什么场景使用核心动画最多?
在转场动画中,核心动画的类型比较多
根据一个路径做动画,只能用核心动画(帧动画)
动画组:同时做多个动画

原文地址:https://www.cnblogs.com/sgxx/p/7120100.html