IOS基础视图:绘图

原创:知识点总结性文章
创作不易,请珍惜,之后会持续更新,不断完善
个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈哈,这些文章记录了我的IOS成长历程,希望能与大家一起进步
温馨提示:由于简书不支持目录跳转,大家可通过command + F 输入目录标题后迅速寻找到你所需要的内容

目录

  • 比较
  • 一、Bitmap 位图
    • 1、Bitmap的概念
    • 2、iOS中的Bitmap
  • 二、CoreGraphics
    • 1、简介
    • 2、绘图概念
    • 3、drawRect: 常用方法
    • 4、drawRect: 自定义view的步骤
    • 5、drawRect: 绘制直线
    • 6、drawRect: 绘制曲线
    • 7、drawRect: 绘制几何图形
    • 8、drawRect: 绘制功能块
    • 9、drawRect: 绘制文字和图片
    • 10、drawRect: 修改图形
  • 三、CAShapeLayer 和 CAGradientLayer
    • 1、CAShapeLayer
    • 2、CAGradientLayer
  • Demo
  • 参考文献

一、Bitmap 位图

1、Bitmap的概念

位图(Bitmap),又称点阵图,是使用像素阵列来表示的图像。位图的像素都分配有特定的位置和颜色值。每个像素的颜色信息由RGB组合或者灰度值表示。根据位深度可将位图分为1、4、8、16、24及32位图像等。每个像素使用的信息位数越多,可用的颜色就越多,颜色表现就越逼真,相应的数据量越大。例如,位深度为 1 的像素位图只有两个可能的值(黑色和白色),所以又称为二值位图。位深度为 8 的图像有 2⁸(即 256)个可能的值。位深度为 8 的灰度模式图像有 256 个可能的灰色值。RGB图像由三个颜色通道组成。8 位通道的RGB图像中的每个通道有 256 个可能的值,这意味着该图像有 1600 万个以上可能的颜色值。有时将带有 8 位通道(bpc)RGB图像称作 24 位图像。通常将使用24位RGB组合数据位表示的的位图称为真彩色位图。

由上面的描述可知,我们可以将bitmap理解为一个点阵图或者是一个数组,其中的每个元素都是一个像素信息,假设对于一个32位RGBA图像来说,则每个元素包含着三个颜色组件(R,G,B)和一个Alpha组件,每一个组件占8位(8bite = 1byte = 32 / 4)。这些像素集合起来就可以表示出一张图片。


2、iOS中的Bitmap

Bitmap的数据由CGImageRef封装。由以下几个函数可以创建CGImageRef对象。

CGImageCreate //最灵活,但也是最复杂的一种方式,要传入11个参数 
CGImageSourceCreate:ImageAtIndex //通过已经存在的Image对象来创建
CGImageSourceCreate:ThumbnailAtIndex //和上一个函数类似,不过这个是创建缩略图
CGBitmapContextCreateImage //通过Copy Bitmap Graphics来创建
CGImageCreateWith:ImageInRect //通过在某一个矩形内数据来创建

如果要使用bitmap对图片进行各种处理,则需要先创建位图上下文。先看一下初始化方法的一个例子。

let w = Int(image.size.width)// 位图的宽和高
let h = Int(image.size.height)
let bitsPerComponent = 8// 颜色组件或者alpha组件占的bite数
let bytesPerRow = w * 4// 位图的每一行占的字节数
let colorSpace = CGColorSpaceCreateDeviceRGB()// 颜色空间,是RGBA、CMYK还是灰度值
let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Big.rawValue// 一个常量,描述这个位图上下文所对应的位图的基本信息

let bufferData = UnsafeMutablePointer<UInt32>.allocate(capacity: w * h)
bufferData.initialize(repeating: 0, count: w * h)

let cxt = CGContext(data: bufferData, //data: 用于存放位图的点阵数据,当生成上下文并调用 CGContextDrawImage 方法将指定图片绘制进上下文之后,data里面就会有该图片的位图像素信息,可以当做一个数组指针来使用。我们可以对这个data里面的内容进行操作,然后以这个data为主要参数通过生成 CGDataProvider 实例并调用 CGImageCreate 方法来重新生成一个CGImage
                            width: w,// 位图的宽和高。
                            height: h,// 如width = 10,height = 20则代表每一行有10个像素,每一列有20个像素。
                            bitsPerComponent: bitsPerComponent,// 颜色组件或者alpha组件占的bite数。以32位图像为例:bitsPerComponent = 8
                            bytesPerRow: bytesPerRow,// 位图的每一行占的字节数。以32位图像为例:一个像素有4byte(rgba),那么bytesPerRow = width * 4
                            space: colorSpace,// 颜色空间,是RGBA、CMYK还是灰度值。RGBA : CGColorSpaceCreateDeviceRGB( )、 CMYK : CGColorSpaceCreateDeviceCMYK( ) 、灰度值 : CGColorSpaceCreateDeviceGray( )
                            bitmapInfo: bitmapInfo)// 一个常量,描述这个位图上下文所对应的位图的基本信息。通常是多个枚举值做或运算的最终值(CGBitmapInfo 和 CGImageAlphaInfo)。比如可以置顶是否具有alpha通道,alpha通道的位置(是RGBA还是ARGB),字节排列的顺序等等

二、CoreGraphics

1、简介

a、UIKit

它是高级别的图形接口,它的API都是基于Objective-C的。它能够访问绘图、动画、字体、图片等内容。UIkit中坐标系的原点在左上角,而Quartz 2D的坐标系的原点在左下角。

b、Quartz2D

它是一个二维(二维即平面)绘图引擎(封装的一套用于绘图的函数库),同时支持iOS和Mac系统(可以跨平台开发)。API(应用程序界面)是纯C语言的,来自于Core Graphics框架,其数据类型和函数基本都以CG作为前缀。

Quartz2D作用
  • iOS中大部分系统控件的内容都是通过Quartz2D画出来的,有些UI界面极其复杂、而且比较个性化,用普通的UI控件无法实现,这时可以利用Quartz2D技术,通过重写drawRect:方法将其画出来
  • 裁剪图片(矩形图片裁剪成圆形图片)
  • 基于路径的绘图,如线条、三角形、矩形、圆、弧等
  • 透明度绘图、遮盖、阴影
  • 颜色管理、防锯齿渲染

2、绘图概念

position

它是用来设置当前的layer在父控件当中的位置的,默认以父控件左上角的(0.0)点为它的坐标原点。

anchorPoint

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

setNeedsDisplay

调用会重新绘制整个视图,此时系统会自动帮你调用drawRect方法。

setNeedsDisplayInRect

重新绘制视图的部分区域。最好不要绘制视图的全部,以减少绘制带来开销。

setNeedsLayout

标记为需要重新布局,会异步调用layoutIfNeeded刷新布局。不会立即刷新,而是在下一轮runloop结束前刷新,对于这一轮runloop之内的所有布局和UI上的更新只会刷新一次。

修改了当前视图的size、设置了不同的frame或者调用了addsubViews,都是会被系统自动给你标记为setNeedsLayout的,然后调用layoutSubviews进行重新布局。

layoutIfNeeded

如果发现有需要刷新的标记,立即调用layoutSubviews进行布局。如果想在当前runloop中立即刷新,调用顺序应该是:

[self setNeedsLayout];
[self layoutIfNeeded];
layoutSubviews

将继承于UIView的子类进行布局更新来刷新视图。如果某个视图自身的bounds或者子视图的bounds发生改变,那么这个方法会在当前runloop结束的时候被调用。为什么不是立即调用呢?因为渲染毕竟比较消耗性能,特别是视图层级复杂的时候。在这种机制下任何UI控件布局上的变动不会立即生效,而是每次间隔一个周期,所有UI控件在布局上的变动统一生效再在视图上一起更新,苹果通过这种高性能的机制保障了视图渲染的流畅性。

重新绘制的流程

runloopobserver回调=>CoreAnimation渲染引擎一次事务的提交=>CoreAnimation递归查询图层是否有布局上的更新=>CALayer layoutSublayers=>UIView layoutSubviews。从这里调用的流程也可以看出UIView其实就是相当于CALayer的代理。

触发视图重新绘制的动作
  • 当遮挡你的视图的其他视图被移动或删除
  • 将视图的hidden属性声明设置为NO,使其从隐藏状态变为可见
  • 将视图滚出屏幕,然后再重新回到屏幕上
  • 显式调用视图的setNeedsDisplay或者setNeedsDisplayInRect:方法
  • 当继承自UIView的视图被创建,设置frame后,并且已经添加到某个view,则会调用;若不设置frame则不会触发drawRect
  • view某一属性例如背景色发生改变时,调用layoutIfNeeded,会触发drawRect;若view中布局或属性没有发生变化,则不会调用
  • view的创建并不会被标记为需要刷新的,除非你设置了一个CGRectZeroframe

3、drawRect: 常用方法

a、拼接路径函数
// 新建一个起点
void CGContextMoveToPoint(CGContextRef c, CGFloat x, CGFloat y)

// 添加新的线段到某个点
void CGContextAddLineToPoint(CGContextRef c, CGFloat x, CGFloat y)

// 添加一个矩形
void CGContextAddRect(CGContextRef c, CGRect rect)

// 添加一个椭圆
void CGContextAddEllipseInRect(CGContextRef context, CGRect rect)

// 添加一个圆弧
void CGContextAddArc(CGContextRef c, CGFloat x, CGFloat y, CGFloat radius, CGFloat startAngle, CGFloat endAngle, int clockwise)
b、绘制路径函数
// Mode参数决定绘制的模式
void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode)

// 绘制空心路径(注意不是线段!)
void CGContextStrokePath(CGContextRef c)

// 绘制实心路径(注意不是线段!)
void CGContextFillPath(CGContextRef c)
c、图形上下文栈的操作
// 将当前的上下文copy一份保存到栈顶(那个栈叫做图形上下文栈)
void CGContextSaveGState(CGContextRef c)

// 将栈顶的上下文出栈,替换掉当前的上下文
void CGContextRestoreGState(CGContextRef c)
d、利用矩阵操作能让绘制到上下文中的所有路径一起发生变化
// 缩放
void CGContextScaleCTM(CGContextRef c, CGFloat sx, CGFloat sy)

// 旋转
void CGContextRotateCTM(CGContextRef c, CGFloat angle)

// 平移
void CGContextTranslateCTM(CGContextRef c, CGFloat tx, CGFloat ty)

4、drawRect: 自定义view的步骤

❶ 新建一个类,继承自UIView
❷ 在 drawRect: 方法实现下述几步,注意只有在 drawRect: 方法中才能拿到图形上下文进行画图
- (void)drawRect:(CGRect)rect 
{
    
}
❸ 取得跟当前 view 相关联的图形上下文
// 固定操作,上下文压栈
CGContextRef context = UIGraphicsGetCurrentContext();
UIGraphicsPushContext(context);
❹ 拼接路径,绘制图形内容
//贝塞尔曲线
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:leftTop];
[path addLineToPoint:anchor];
[path addLineToPoint:leftBottom];
[path closePath];
❺ 把路径添加到上下文
CGContextAddPath(context, path.CGPath);
❻ 设置绘图状态
// 描边宽度
CGContextSetLineWidth(context, lineWidth);
// 描边颜色
CGContextSetStrokeColorWithColor(context, lineColor.CGColor);
CGContextSetFillColorWithColor(context, timeLineColor.CGColor);
❼ 利用图形上下文将绘制的所有内容渲染显示到 view 上面
CGContextStrokePath(context);
CGContextFillPath(context);

5、drawRect: 绘制直线

只有在drawRect方法中才能拿到图形上下文,才可以画图
- (void)drawRect:(CGRect)rect
{
    [self drawStraightLine];
}

- (void)drawStraightLine
{
    // 获取图形上下文
    CGContextRef context = UIGraphicsGetCurrentContext();
    ...
}
a、一条直线
一条直线
❶ 直接使用图形上下文画图
 // 描述路径
CGContextMoveToPoint(context, 10, 19);// 起始点
CGContextAddLineToPoint(context, 100, 65);// 添加线

// 完成路线绘制
CGContextStrokePath(context);
❷ 使用图形上下文+CGPathRef画线
// 使用path来画线
CGMutablePathRef path = CGPathCreateMutable();
// 添加点
CGPathMoveToPoint(path, NULL, 34, 23);
CGPathAddLineToPoint(path, NULL, 100, 43);
// 将path添加到图像上下文上
CGContextAddPath(context, path);
// 渲染上下文
CGContextStrokePath(context);
❸ 可以只使用贝塞尔曲线画线,不获取图形上下文,因为在底层系统已经给封装了
// 1.创建路径
UIBezierPath *bezierPath = [UIBezierPath bezierPath];
// 2.画线
[bezierPath moveToPoint:CGPointMake(80, 45)];
[bezierPath addLineToPoint:CGPointMake(150, 88)];
// 3.渲染绘制
[bezierPath stroke];
❹ 同时使用图形上下文+贝塞尔曲线画线
// 使用贝塞尔曲线和图形上下文画图
 UIBezierPath *contextPath = [UIBezierPath bezierPath];
 [contextPath moveToPoint:CGPointZero];
 [contextPath addLineToPoint:CGPointMake(233, 69)];

// 这个是C语言的写法,必须使用path.CGPath,否则无效
 CGContextAddPath(context, contextPath.CGPath);
 CGContextStrokePath(context);

用四种方法的目的是说明绘制图形有很多种方法。绘制图形实际上就是设置path,底层的用的都是CGMutablePathRef。使用贝塞尔曲线画图的好处在于,每一个贝塞尔底层都有一个图形上下文,如果是用CGContextMoveToPoint画图,实际上就是一个图形上下文,不好去控制,所以建议多条线可以使用贝塞尔曲线。不推荐使用第4种方式,两个东西杂糅,不太好。


b、相交直线
相交直线
- (void)drawIntersectingLines
{
    // 1.获取图形上文
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    // 2.绘制路径,并且将其添加到图形上下文
    CGContextMoveToPoint(context,123, 45);// 起始点
    CGContextAddLineToPoint(context, 45, 80);// 添加一根线
    CGContextAddLineToPoint(context, 223, 159);// 添加另一根线

    // 3.设置路径属性
    [[UIColor greenColor] set];// 设置颜色,这种设置方式就不用考虑实线还是填充图形了
    CGContextSetLineWidth(context, 10);// 设置线的宽度
    CGContextSetLineJoin(context, kCGLineJoinMiter);// 设置链接处的链接类型
    CGContextSetLineCap(context, kCGLineCapButt);// 设置线的头部样式

    // 4.渲染绘制
    CGContextStrokePath(context);
}

c、两条不相交的线段
两条不相交的线段
- (void)drawTwoLine
{
    // 设置红色线段
    UIBezierPath *redPath = [UIBezierPath bezierPath];
    [redPath moveToPoint:CGPointMake(12, 49)];
    [redPath addLineToPoint:CGPointMake(68, 34)];
    [redPath setLineWidth:3];
    [[UIColor redColor] set];
    [redPath stroke];

    // 设置绿色线段
    UIBezierPath *greenPath = [UIBezierPath bezierPath];
    [greenPath moveToPoint:CGPointMake(145, 167)];
    [greenPath addLineToPoint:CGPointMake(98, 34)];
    [greenPath setLineWidth:10];
    [[UIColor greenColor] set];
    [greenPath setLineCapStyle:kCGLineCapRound];
    [greenPath stroke];
}

d、绘制温度计🌡️
温度计
❶ 配置线条属性
- (void)drawThermometerLine
{
    // 获取图形上文
    CGContextRef context = UIGraphicsGetCurrentContext();
    UIGraphicsPushContext(context);// 压入上下文
    
    // 配置线条属性
    CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);// 红线
    CGContextSetLineWidth(context, 5.0);// 线宽
    ...
    // 渲染绘制线条
    CGContextStrokePath(context);
    ...
}
❷ 画线:线帽在底部
if (!_isTop)
{
    CGPoint top = CGPointMake(150, 0);// 线顶
    CGPoint cycleTop = CGPointMake(150, 16 - 6 - 5);// 线帽顶
    
    // 创建线条路径
    UIBezierPath *topPath = [UIBezierPath bezierPath];
    [topPath moveToPoint:top];
    [topPath addLineToPoint:cycleTop];
    CGContextAddPath(context, topPath.CGPath);
}
❸ 画线:线帽在顶部
if (!_isBottom)// 线帽在顶部
{
    CGPoint cycleBottom = CGPointMake(150, 16 + 6 + 5);// 线帽底
    CGPoint bottom = CGPointMake(150, height);// 线底
    
    // 创建线条路径
    UIBezierPath *bottomPath = [UIBezierPath bezierPath];
    [bottomPath moveToPoint:cycleBottom];
    [bottomPath addLineToPoint:bottom];
    CGContextAddPath(context, bottomPath.CGPath);
}
❹ 画线帽
CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);// 黄色
CGPoint center = CGPointMake(150, 16);// 圆心

UIBezierPath *cyclePath = [UIBezierPath bezierPath];
[cyclePath moveToPoint:CGPointMake(center.x, center.y+6)];// 圆心
[cyclePath addArcWithCenter:center radius:6 startAngle:0 endAngle:M_PI*2 clockwise:YES];// 圆
[cyclePath closePath];// 闭合
CGContextAddPath(context, cyclePath.CGPath);
CGContextFillPath(context);
❺ 温度计上下颠倒
- (void)isTop:(BOOL)isTop isBottom:(BOOL)isBottom
{
    _isTop = isTop;
    _isBottom = isBottom;
    [self setNeedsDisplay];
}

e、绘制点线
绘制点线
- (void)drawDotLine
{
    // 获取图形上文
    CGContextRef context = UIGraphicsGetCurrentContext();
    UIGraphicsPushContext(context);// 压入上下文
    
    // 设置线属性
    UIColor *lineColor = [UIColor redColor];
    CGFloat height = 10;
    
    CGContextSetLineWidth(context, 10);// 点的高度
    CGContextSetStrokeColorWithColor(context, lineColor.CGColor);// 红线
    CGFloat lengths[] = {3, 2};// 表示先绘制3个点,再跳过2个点,如此反复
    // phase参数表示在第一个虚线绘制的时候跳过多少个点,这里为0
    CGContextSetLineDash(context, 0, lengths, 2);// 2表示lengths数组的长度
    
    // 绘制线
    CGPoint left = CGPointMake(20, height/2);
    CGPoint right = CGPointMake(200, height/2);
    
    UIBezierPath *path = [UIBezierPath bezierPath];
    [path moveToPoint:left];// 起始点
    [path addLineToPoint:right];// 结束点
    
    // 将线添加到上下文
    CGContextAddPath(context, path.CGPath);
    
    // 渲染绘制
    CGContextStrokePath(context);
}

6、drawRect: 绘制曲线

a、绘制一条曲线
绘制一条曲线
- (void)drawCurve
{
    // 获取图形上文
    CGContextRef context = UIGraphicsGetCurrentContext();

    // 设置起点
    CGContextMoveToPoint(context, 10, 10);
    
    // 需要在图形上下文中配置要突出点的x和y值以及曲线结束点的x和y值
    CGContextAddQuadCurveToPoint(context, 50, 50, 100, 10);

    // 设置颜色
    [[UIColor redColor] set];

    // 渲染图层
    CGContextStrokePath(context);
}

7、drawRect: 绘制几何图形

a、三角形、圆、圆饼、心
三角形、圆、圆饼、心
❶ 配置线条属性
- (void)collectionView
{
    CGContextRef context = UIGraphicsGetCurrentContext();
    UIGraphicsPushContext(context);

    UIColor *timeLineColor = [UIColor redColor];
    CGContextSetStrokeColorWithColor(context, timeLineColor.CGColor);// 描边颜色
    CGContextSetLineWidth(context, 2.0);// 描边线宽

    ...
    CGContextFillPath(context);// 填充
}
❷ 三角形
CGPoint triangleTop = CGPointMake(50, 10);// 顶点
CGPoint triangleLeft = CGPointMake(0, 60);// 左点
CGPoint triangleRight = CGPointMake(100, 60);// 右点

UIBezierPath *trianglePath = [UIBezierPath bezierPath];// 创建路径
[trianglePath moveToPoint:triangleTop];// 起始点
[trianglePath addLineToPoint:triangleLeft];// 左边
[trianglePath addLineToPoint:triangleRight];// 右边
[trianglePath closePath];// 闭合

CGContextAddPath(context, trianglePath.CGPath);// 添加路径
❸ 圆
CGPoint strokeCenter = CGPointMake(150, 30);// 圆心
UIBezierPath *strokeCyclePath = [UIBezierPath bezierPathWithArcCenter:strokeCenter radius:25 startAngle:0 endAngle:M_PI*2 clockwise:YES];// 圆形
[strokeCyclePath closePath];// 闭合

CGContextAddPath(context, strokeCyclePath.CGPath);// 添加路径
CGContextStrokePath(context);// 描边
CGContextSetFillColorWithColor(context, timeLineColor.CGColor);// 填充颜色
❹ 圆饼
CGPoint fillCenter = CGPointMake(220, 30);// 圆心
UIBezierPath *fillCyclePath = [UIBezierPath bezierPathWithArcCenter:fillCenter radius:25 startAngle:0 endAngle:M_PI*2 clockwise:YES];// 圆形
[fillCyclePath closePath];// 闭合

CGContextAddPath(context, fillCyclePath.CGPath);// 添加路径
❺ 心:双圆弧+双贝塞尔曲线
CGPoint startPoint = CGPointMake(260, 30);// 起始点

UIBezierPath *heartPath = [UIBezierPath bezierPath];// 创建路径
[heartPath moveToPoint:startPoint];// 起始点
[heartPath addArcWithCenter:CGPointMake(280, 30) radius:20 startAngle:-M_PI endAngle:0 clockwise:YES];// 圆弧1
[heartPath addArcWithCenter:CGPointMake(320, 30) radius:20 startAngle:-M_PI endAngle:0 clockwise:YES];// 圆弧2
[heartPath addCurveToPoint:CGPointMake(300, 80) controlPoint1:CGPointMake(330, 60) controlPoint2:CGPointMake(330, 60)];// 贝塞尔曲线1
[heartPath addCurveToPoint:startPoint controlPoint1:CGPointMake(270, 60) controlPoint2:CGPointMake(270, 60)];// 贝塞尔曲线2
[heartPath closePath];// 闭合
CGContextAddPath(context, heartPath.CGPath);// 添加路径

b、圆角矩形
圆角矩形
- (void)drawRoundedRectangle
{
    // 绘制一个圆角矩形
    UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(10, 10, 100, 40) cornerRadius:5];
    [[UIColor orangeColor] set];
    [path stroke];// 设置边框的颜色

    // 绘制一个圆角矩形
    UIBezierPath *path2 = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(10, 110, 100, 40) cornerRadius:5];
    [[UIColor greenColor] set];
    [path2 fill];// fill填充内部的颜色,必须是封闭的图形

    // 绘制一个圆形(不规范的绘制法:圆角直径是正方形的边长)
    UIBezierPath *path3 = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(140, 10, 100, 100) cornerRadius:50];
    [[UIColor cyanColor] set];
    [path3 fill];
}

c、弧线
弧线
- (void)drawArc
{
    /** 绘制弧度曲线
     *  ArcCenter 曲线中心
     *  radius       半径
     *  startAngle 开始的弧度
     *  endAngle 结束的弧度
     *  clockwise YES顺时针,NO逆时针
     */
    UIBezierPath *greenPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(100, 100) radius:14 startAngle:0 endAngle:M_PI clockwise:YES];
    [[UIColor greenColor] set];
    [greenPath stroke];

    // 结束角度如果是 M_PI * 2 就是一个圆
    // M_PI是180度 M_PI_2是90度
    UIBezierPath *purplePath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(240, 140) radius:40 startAngle:0 endAngle:M_PI * 2 clockwise:YES];
    [[UIColor purpleColor] set];
    [purplePath fill];

    // 绘制115度角的圆弧
    // 这里的角度都是弧度制度,如果我们需要15°,可以用15°/180°*π得到
    UIBezierPath *orangePath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(160, 100) radius:20 startAngle:0 endAngle:115/360.0*(M_PI *2) clockwise:NO];
    [[UIColor orangeColor] set];
    [orangePath stroke];
}

d、扇形
扇形
- (void)drawSector
{
    // 获取图形上下文
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    // 绘制曲线
    CGFloat centerX = 100;
    CGFloat centerY = 100;
    CGFloat radius = 30;
    
    // 添加一个点
    CGContextMoveToPoint(context, centerX, centerY);
    // 添加一个圆弧
    CGContextAddArc(context, centerX, centerY, radius, M_PI, M_PI_2, NO);
    // 关闭线段
    CGContextClosePath(context);
    // 渲染
    CGContextStrokePath(context);
}

8、drawRect: 绘制功能块

a、下载进度
进度条
❶ 绘制表示下载进度的⭕️
- (void)drawDownloadProgress
{
    CGFloat radius = self.frame.size.height * 0.5 - 10;// 半径
    CGFloat endAadius = self.progress * M_PI * 2 - M_PI_2;// 结束点
    
    // 创建路径
    UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(radius+5,radius+5) radius:radius-5 startAngle:-M_PI_2 endAngle:endAadius clockwise:YES];// 弧线
    path.lineWidth = 10;// 线宽
    path.lineCapStyle = kCGLineCapRound;// 圆角
    [[UIColor redColor] set];// 红色
    [path stroke];// 绘制
}
❷ 调用改变下载进度的事件
- (void)changeProgress:(id)sender
{
    if ([sender isKindOfClass:[UISlider class]])
    {
        UISlider *slider = sender;
        self.drawView.progress = slider.value;
    }
}
❸ 要想重复多次调用drawRect:(CGRect)rect方法,必须要手动使用setNeedsDisplay,否则无效
- (void)setProgress:(CGFloat)progress
{
    _progress = progress;

    [self setNeedsDisplay];
}

b、饼状图
饼状图
❶ 各块披萨上面的数字
-(NSArray *)nums
{
    if (!_nums)
    {
        _nums = @[@23,@34,@33,@13,@30,@44,@66];
    }
    return _nums;
}
❷ 所有披萨的数字和
- (NSInteger)total
{
    if (_total == 0)
    {
        for (int j = 0; j < self.nums.count; j++)
        {
            _total += [self.nums[j] integerValue];
        }
    }
    return _total;
}
❸ 绘制饼状图形
- (void)drawPieChart
{
    CGPoint center = CGPointMake(100, 100);// 圆心
    CGFloat radius = 50;// 半径
    CGFloat startAngle = 0;// 开始角度
    CGFloat endAngle = 0;// 结束角度
    
    for (int i = 0; i < self.nums.count; I++)
    {
        NSNumber *data = self.nums[i];// 各块披萨上面的数字
        
        // startAngle跳转到最新的endAngle的位置
        startAngle = endAngle;
        
        // 重新计算endAngle的最新位置
        endAngle = startAngle + [data floatValue]/self.total * 2 * M_PI;
        
        // 路径
        UIBezierPath *path  = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES];
        
        // 连接弧形和圆心
        [path addLineToPoint:center];
        
        // 填充随机颜色
        CGFloat randRed = arc4random_uniform(256)/255.0;
        CGFloat randGreen = arc4random_uniform(256)/255.0;
        CGFloat randBlue = arc4random_uniform(256)/255.0;
        UIColor *randomColor = [UIColor colorWithRed:randRed green:randGreen blue:randBlue alpha:1.0];
        [randomColor set];
        [path fill];
    }
}

c、柱状图
柱状图
❶ 各柱子上面的数字
- (NSArray *)barNums
{
    if (!_barNums)
    {
        _barNums = @[@23,@34,@93,@2,@55,@46];
    }
    return _barNums;
}
❷ 绘制柱状图
- (void)drawBarChart:(CGRect)rect
{
    // 获取图形上下文
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    // 两个柱状间距
    CGFloat margin = 30;
    
    // 每个柱状宽度 = (总宽度 - 柱状间距的宽度和)/ 柱状个数
    CGFloat width = (rect.size.width - (self.barNums.count + 1) * margin) / self.barNums.count;
    
    for (int i = 0; i < self.barNums.count; I++)
    {
        // 柱X坐标
        CGFloat x = margin + (width + margin) * I;
        
        // 计算柱Y坐标 = 总高度 - 柱状高度
        CGFloat num = [self.barNums[i] floatValue] / 100;// 柱所代表的数字
        CGFloat y = rect.size.height * (1 - num);// y坐标
        CGFloat height = rect.size.height * num;// 柱高度
        
        // 绘柱图
        CGRect barRect =  CGRectMake(x, y, width, height);
        CGContextAddRect(context,barRect);
        
        // 填充随机颜色
        CGFloat randRed = arc4random_uniform(256)/255.0;
        CGFloat randGreen = arc4random_uniform(256)/255.0;
        CGFloat randBlue = arc4random_uniform(256)/255.0;
        UIColor *randomColor = [UIColor colorWithRed:randRed green:randGreen blue:randBlue alpha:1.0];
        [randomColor set];

        CGContextFillPath(context);
    }
}

d、评分星星
评分星星
❶ 声明属性
@property (nonatomic, assign, readwrite) int starRadius;// 星星半径
@property (nonatomic, assign, readwrite) int defaultNumber;// 默认画五个星
@property (nonatomic, assign, readwrite) int starWithColorNumber;// 有几颗星星需要填充颜色
@property (nonatomic, strong, readwrite) UIColor *starColor;// 描边颜色
❷ 绘制画评分星星
- (void)drawStar
{
    CGFloat innerRadius = self.starRadius*sin(M_PI/10)/cos(M_PI/5);
    
    for (int i = 0; i < self.defaultNumber; I++)
    {
        // 创建星星视图
        UIView *starView = [[UIView alloc] initWithFrame:CGRectMake(2*self.starRadius*cos(M_PI/10)*i, 0, 2*self.starRadius*cos(M_PI/10), self.starRadius+self.starRadius*cos(M_PI/10))];
        
        // 创建星星形状图层
        CAShapeLayer *starMask = [CAShapeLayer layer];
        
        // 第一个点
        CGPoint firstPoint = CGPointMake(self.starRadius, 0);
        // 第二个点
        CGPoint secondPoint = CGPointMake(self.starRadius+innerRadius*sin(M_PI/5), self.starRadius - innerRadius*cos(M_PI/5));
        // 第三个点
        CGPoint thirdPoint = CGPointMake(self.starRadius*cos(M_PI/10)+self.starRadius, self.starRadius-self.starRadius*sin(M_PI/10));
        // 第四个点
        CGPoint forthPoint = CGPointMake(self.starRadius + innerRadius*sin(3*M_PI/10), self.starRadius + innerRadius*cos(3*M_PI/10));
        // 第五个点
        CGPoint fifthPoint = CGPointMake(self.starRadius*cos(M_PI*3/10)+self.starRadius, self.starRadius+self.starRadius*sin(M_PI*3/10));
        // 第六个点
        CGPoint sixthPoint = CGPointMake(self.starRadius, self.starRadius+innerRadius);
        // 第七个点
        CGPoint seventhPoint = CGPointMake(self.starRadius-self.starRadius*cos(M_PI*3/10), self.starRadius+self.starRadius*sin(M_PI*3/10));
        // 第八个点
        CGPoint eighthPoint = CGPointMake(self.starRadius - innerRadius*sin(3*M_PI/10), self.starRadius + innerRadius*cos(3*M_PI/10));
        // 第九个点
        CGPoint ninethPoint = CGPointMake(self.starRadius-self.starRadius*cos(M_PI/10), self.starRadius-self.starRadius*sin(M_PI/10));
        // 第十个点
        CGPoint tenthPoint = CGPointMake(self.starRadius - innerRadius*sin(M_PI/5), self.starRadius - innerRadius*cos(M_PI/5));
        
        // 创建路径图
        UIBezierPath *starMaskPath = [UIBezierPath bezierPath];
        [starMaskPath moveToPoint:firstPoint];
        [starMaskPath addLineToPoint:secondPoint];
        [starMaskPath addLineToPoint:thirdPoint];
        [starMaskPath addLineToPoint:forthPoint];
        [starMaskPath addLineToPoint:fifthPoint];
        [starMaskPath addLineToPoint:sixthPoint];
        [starMaskPath addLineToPoint:seventhPoint];
        [starMaskPath addLineToPoint:eighthPoint];
        [starMaskPath addLineToPoint:ninethPoint];
        [starMaskPath addLineToPoint:tenthPoint];
        [starMaskPath closePath];// 闭合路径
        starMask.path = starMaskPath.CGPath;// 星星形状图层的路径
        starMask.strokeColor = _starColor.CGColor;// 绘边颜色
        
        // 填充颜色
        if (i < self.starWithColorNumber)
        {
            starMask.fillColor = _starColor.CGColor;
        }
        else
        {
            starMask.fillColor = UIColor.whiteColor.CGColor;
        }
        
        // 添加星星形状图层到视图
        [starView.layer addSublayer:starMask];
        [self addSubview:starView];
    }
}
❸ 使用方式
- (void)createStarView
{
    int starRadius = 30;// 外接圆半径
    int defaultNumber = 5;// 默认画五个星
    
    self.star = [[DrawFunctionView alloc] initWithFrame:CGRectZero];
    self.starViewHeight = starRadius+starRadius * cos(M_PI/10);// 星星高度
    self.starViewWidth = 2 * defaultNumber*starRadius * cos(M_PI/10);// 星星宽度
    self.star.starColor = [UIColor redColor];// 描边颜色
    self.star.starWithColorNumber = 2;// 有几颗星星需要填充颜色
    self.star.starRadius = starRadius;// 星星半径
    self.star.defaultNumber = defaultNumber;// 默认画五个星
    
    self.star.backgroundColor = [UIColor whiteColor];
    [self.view addSubview:self.star];
    [self.star mas_makeConstraints:^(MASConstraintMaker *make) {
        make.height.width.equalTo(@300);
        make.center.equalTo(self.view);
    }];
}

9、drawRect: 绘制文字和图片

a、绘制图片
  • drawAtPoint:从指定的点为图片的左上角的起点开始绘制,绘制出来的图形跟图片尺寸一样大,图片是按照原始大小进行绘制,图片的大小超出当前view的视图范围,则无法进行绘制
  • drawInRect:在指定的rect区域内绘制整张图片,图片会按照指定区域的宽高进行缩放,所以这种方式一定可以显示完整的图片,但是会进行一些缩放
  • drawAsPatternInRect: 在指定的rect区域内平铺图片,如果一张图片不够用,则会在剩下的地方重新放置该图片,图片的大小尺寸不会改变
绘制文字和图片的时候,不用去获取图像上下文
- (void)drawImage:(CGRect)rect
{
    // 1.用矩形去填充一个区域
    UIRectFill(rect);
    
    // 2.绘制一个矩形的边框
    UIRectFrame(rect);
    
    // 3.剪切图片,超出的图片位置都要剪切掉!必须要在绘制之前写,否则无效
    UIRectClip(CGRectMake(20, 20, 400, 400));
    
    // 4.创建图片
    UIImage *image = [UIImage imageNamed:@"luckcoffee.JPG"];
    // 在什么范围下
    [image drawInRect:rect];
    // 在哪个位置开始画
    //[image drawAtPoint:CGPointMake(10, 10)];
    // 平铺
    //[image drawAsPatternInRect:rect];
}

b、绘制富文本
绘制文本
- (void)drawText
{
    NSString *str = @"遂以为九天之上,有诸般神灵,九幽之下,亦是阴魂归处,阎罗殿堂。";
    
    // 设置文字的属性
    NSMutableDictionary *paras = [NSMutableDictionary dictionary];
    paras[NSFontAttributeName] = [UIFont systemFontOfSize:40];// 字号
    paras[NSForegroundColorAttributeName] = [UIColor redColor];// 红色字体
    paras[NSStrokeColorAttributeName] = [UIColor blackColor];// 橘色描边
    paras[NSStrokeWidthAttributeName] = @3;// 橘色宽度

    // 创建阴影对象
    NSShadow *shodow = [[NSShadow alloc] init];
    shodow.shadowColor = [UIColor blueColor];// 阴影颜色
    shodow.shadowOffset = CGSizeMake(5, 6);// 阴影偏移
    shodow.shadowBlurRadius = 4;// 阴影模糊半径
    paras[NSShadowAttributeName]  = shodow;// 阴影属性
    
    // 富文本
    [str drawAtPoint:CGPointZero withAttributes:paras];
}
 NSFontAttributeName                设置字体属性,默认值:字体:Helvetica(Neue) 字号:12
 NSForegroundColorAttributeName      设置字体颜色,取值为 UIColor 对象,默认值为黑色
 NSBackgroundColorAttributeName     设置字体所在区域背景颜色,取值为 UIColor对象,默认值为nil, 透明色
 NSLigatureAttributeName            设置连体属性,取值为NSNumber 对象(整数),0 表示没有连体字符,1 表示使用默认的连体字符
 NSKernAttributeName                设定字符间距,取值为 NSNumber 对象(整数),正值间距加宽,负值间距变窄
 NSStrikethroughStyleAttributeName  设置删除线,取值为 NSNumber 对象(整数)
 NSStrikethroughColorAttributeName  设置删除线颜色,取值为 UIColor 对象,默认值为黑色
 NSUnderlineStyleAttributeName      设置下划线,取值为 NSNumber 对象(整数),枚举常量NSUnderlineStyle中的值,与删除线类似
 NSUnderlineColorAttributeName      设置下划线颜色,取值为 UIColor 对象,默认值为黑色
 NSStrokeWidthAttributeName         设置笔画宽度,取值为 NSNumber 对象(整数),负值填充效果,正值中空效果
 NSStrokeColorAttributeName         填充部分颜色,不是字体颜色,取值为 UIColor 对象
 NSShadowAttributeName              设置阴影属性,取值为 NSShadow 对象
 NSTextEffectAttributeName          设置文本特殊效果,取值为 NSString 对象,目前只有图版印刷效果可用
 NSBaselineOffsetAttributeName      设置基线偏移值,取值为 NSNumber (float),正值上偏,负值下偏
 NSObliquenessAttributeName         设置字形倾斜度,取值为 NSNumber (float),正值右倾,负值左倾
 NSExpansionAttributeName           设置文本横向拉伸属性,取值为 NSNumber (float),正值横向拉伸文本,负值横向压缩文本
 NSWritingDirectionAttributeName    设置文字书写方向,从左向右书写或者从右向左书写
 NSVerticalGlyphFormAttributeName   设置文字排版方向,取值为 NSNumber 对象(整数),0 表示横排文本,1 表示竖排文本
 NSLinkAttributeName                设置链接属性,点击后调用浏览器打开指定URL地址
 NSAttachmentAttributeName          设置文本附件,取值为NSTextAttachment对象,常用于文字图片混排
 NSParagraphStyleAttributeName      设置文本段落排版格式,取值为 NSParagraphStyle 对象

c、雪花飘动
雪花飘动
❶ 绘制雪花
// 每秒计时器都会调用
- (void)drawSnow:(CGRect)rect
{
    // 设置下雪的动画
    UIImage *image = [UIImage imageNamed:@"雪花.jpg"];
    
    // 全局变量,每次+10
    _snowY += 10;
    
    // 定点绘图
    [image drawAtPoint:CGPointMake(150, _snowY)];

    // 置0回到开头
    if (_snowY >= rect.size.height)
    {
        _snowY = 0;
    }
}
❷ 创建计时器
// 如果在绘图的时候需要用到定时器,通常使用CADisplayLink
// NSTimer很少用于绘图,因为调度优先级比较低,并不会准时调用
- (void)createSnowTimer
{
    // 创建定时器
    CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(timeChange)];
    
    // 添加到主运行循环
    [link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}
❸ 计时器调用的事件
// CADisplayLink:每次屏幕刷新的时候就会调用,屏幕一般一秒刷新60次
- (void)timeChange
{
    [self setNeedsDisplay];
}

d、编辑图片

实现图片的水印、裁剪、截屏、压缩等效果,这里以压缩图片为例,其余步骤类似。

压缩图片
- (UIImage*)imageWithImageSimple:(UIImage*)image scaledToSize:(CGSize)newSize
{
    // 开启一个图片上下文
    UIGraphicsBeginImageContext(newSize);

    // 这里就是不同的部分:如压缩图片只需要将旧图片按照新尺寸绘制即可,无需获取当前的上下文
    // CGContextRef context = UIGraphicsGetCurrentContext();
    [image drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];

    // 从上下文当中取出新图片
    UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
    
    // 关闭上下文
    UIGraphicsEndImageContext();
    
    // 返回新图片
    return newImage;
}

10、drawRect: 修改图形

a、修改椭圆
修改椭圆
- (void)drawEllipse
{
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    // 画一个椭圆
    CGPathRef path = CGPathCreateWithEllipseInRect(CGRectMake(0, 0, 200, 100),nil);
    [[UIColor redColor] set];// 红色

    // 偏移
    CGContextTranslateCTM(context, 150, 10);
    // 旋转
    CGContextRotateCTM(context, M_PI_4);
    // 缩放
    CGContextScaleCTM(context, 0.25, 2);

    // 绘图
    CGContextAddPath(context, path);
    CGContextFillPath(context);
}

三、CAShapeLayer 和 CAGradientLayer

1、CAShapeLayer

a、简介

CAShapeLayerCALayer的子类,多用于处理复杂的边缘涂层和边缘动画,虽然该对象也有frame属性,但其本身是没有形状的。使用时应该注意:

  • CAShapeLayer对象是没有固定形状的,必须指定path属性来唯一确定该涂层的形状
  • CAShapeLayer多用于处理边缘动画,不能用来处理填充动画
  • CAShapeLayer中的strokeColor是边缘形状的颜色,fillColor是内部填充部分的颜色(但是两者有重合部分),而且都是CGColorRef对象
  • CAShapeLayer中的strokeStartstrokeEnd是开始位置和结束位置占据整个图形的百分比(0~1)
  • CAShapeLayerline开头的属性用于描述边缘的特性,如lineWidth描述边缘线的线宽,lineCaplineJoin则是用来描述始末段和连接部分的样式。
关于CAShapeLayer和DrawRect的比较
  • DrawRect:属于CoreGraphic框架,占用CPU,消耗性能大
  • CAShapeLayer:属于CoreAnimation框架,通过GPU来渲染图形,节省性能。动画渲染直接提交给手机GPU,不消耗内存。
属性

strokeStart strokeEnd:两者的取值都是0~1,决定贝塞尔曲线的划线百分比,对应值的改变支持隐式动画
lineCap:线端点类型,也就是对应曲线结束的点的显示样式
lineJoin:连接点类型,也就是对应曲线节点的位置的显示样式
lineDashPattern:数组中奇数位实线长度,偶数位虚线长度(注意:这里的奇数,偶数以数组的第一个元素索引为1计算)
lineDashPhase:虚线开始的位置,可以使用此属性做一个滚动的虚线框


b、单独使用CAShapeLayer无效
  • CAShapeLayer继承自CALayer,可使用CALayer的所有属性
  • CAShapeLayer需要和贝塞尔曲线配合使用才有意义。贝塞尔曲线可以为其提供形状。CAShapeLayer 有一个神奇的属性path用这个属性配合上 UIBezierPath 这个类就可以达到超神的效果
  • 使用CAShapeLayer与贝塞尔曲线可以实现不在viewDrawRect方法中画出一些想要的图形
- (void)dot// 圆点
{
    CAShapeLayer *maskLayer = [CAShapeLayer layer];
    maskLayer.frame = CGRectMake(30, 30, 10, 10);
    maskLayer.cornerRadius = 5;
    maskLayer.backgroundColor = [UIColor redColor].CGColor;
    [self.view.layer addSublayer:maskLayer];
}

运行效果一片空白,说明单独使用CAShapeLayer无效。

单独使用CAShapeLayer无效

c、矩形

贝塞尔曲线和图层结合才能绘制出各种各样的图形。当与贝塞尔曲线一起使用的时候,生成的曲线的位置是相对于生成的layer的,所以当你利用贝塞尔曲线设置了path后,再设置layerpositionbounds你会感觉很奇怪,最简单的方式就是单纯利用贝塞尔曲线决定图层的位置和大小。

- (void)rectangle
{
    // X、Y坐标和宽高均起作用了
    UIBezierPath *rectPath = [UIBezierPath bezierPathWithRect:CGRectMake(90, 60, 150, 150)];
    
    CAShapeLayer *maskLayer = [CAShapeLayer layer];
    maskLayer.path = rectPath.CGPath;
    
    maskLayer.lineWidth = 10.0;
    maskLayer.fillColor = [UIColor clearColor].CGColor;
    maskLayer.strokeColor = [UIColor colorWithRed:88/255.0 green:185/255.0 blue:157/255.0 alpha:1].CGColor;
    
    // X、Y坐标起作用了,但是宽度和高度设置何值好像都没影响
    maskLayer.frame = CGRectMake(90, 60, 500, 500);
    
    [self.view.layer addSublayer:maskLayer];
}

d、曲线
普通曲线
- (void)commonCurve
{
    // 创建 path
    UIBezierPath *path = [[UIBezierPath alloc] init];
    CGPoint startPoint = CGPointMake(50, 200);
    CGPoint endPoint = CGPointMake(200, 200);
    CGPoint controlPoint1 = CGPointMake(100, 150);
    CGPoint controlPoint2 = CGPointMake(150, 300);
    [path moveToPoint:startPoint];
    [path addCurveToPoint:endPoint controlPoint1:controlPoint1 controlPoint2:controlPoint2];

    // 创建 shapeLayer
    CAShapeLayer *shapeLayer = [[CAShapeLayer alloc]init];
    [self.view.layer addSublayer:shapeLayer];
    shapeLayer.path = path.CGPath;

    shapeLayer.fillColor = [UIColor clearColor].CGColor;
    shapeLayer.strokeColor = [UIColor blackColor].CGColor;
    shapeLayer.lineWidth = 5;
}

e、直线
普通直线
- (void)ordinaryStraightLine
{
    UIBezierPath *linePathTop = [UIBezierPath bezierPath];
    [linePathTop  moveToPoint:CGPointMake(0, 130)];
    [linePathTop  addLineToPoint:CGPointMake(400, 130)];

    CAShapeLayer *maskLayerTop = [CAShapeLayer layer];
    maskLayerTop.path = linePathTop.CGPath;
    
    maskLayerTop.strokeColor = [UIColor grayColor].CGColor;
    maskLayerTop.lineWidth = 10.0;
    // X、Y坐标起作用了,但是宽度和高度设置何值好像都没影响
    maskLayerTop.frame = CGRectMake(0, 125, 500, 10);

    [self.view.layer addSublayer:maskLayerTop];
}

f、三角形、矩形、圆形
绘制圆形
// 创建圆的路径
UIBezierPath *cyclePath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(30, 30) radius:20 startAngle:0 endAngle:M_PI*2 clockwise:YES];
[cyclePath closePath];// 闭合路径,这里可有可无,圆已经连接完了

// 创建形状图层
CAShapeLayer *cycleLayer = [CAShapeLayer layer];
cycleLayer.path = cyclePath.CGPath;// 路径为圆
cycleLayer.strokeColor = [UIColor redColor].CGColor;// 红色描边
cycleLayer.lineWidth = 10.0;// 线宽
cycleLayer.fillColor = [UIColor clearColor].CGColor;// 空白填充
cycleLayer.frame = CGRectMake(0, 0, 60, 60);// 相对于view
// 添加到view图层中
[self.layer addSublayer:cycleLayer];
绘制三角形
// 三角形的三个点
CGPoint triangleTop = CGPointMake(30, 10);
CGPoint triangleLeft = CGPointMake(10, 50);
CGPoint triangleRight = CGPointMake(50, 50);

// 创建三角形路径
UIBezierPath *trianglePath = [UIBezierPath bezierPath];
[trianglePath moveToPoint:triangleTop];// 起始点
[trianglePath addLineToPoint:triangleLeft];// 连线
[trianglePath addLineToPoint:triangleRight];// 连线
[trianglePath closePath];// 这里必须闭合,否则空了一边没有连接

// 创建形状图层
CAShapeLayer *triangleLayer = [CAShapeLayer layer];
triangleLayer.path = trianglePath.CGPath;// 路径为三角形
triangleLayer.strokeColor = [UIColor blackColor].CGColor;// 黑色描边
triangleLayer.lineWidth = 1.0;// 线宽
triangleLayer.fillColor = [UIColor redColor].CGColor;
triangleLayer.frame = CGRectMake(60, 0, 60, 60);
[self.layer addSublayer:triangleLayer];
绘制矩形
// 矩形路径
UIBezierPath *rectanglePath = [UIBezierPath bezierPath];
[rectanglePath moveToPoint:CGPointMake(10, 10)];// 起始点
[rectanglePath addLineToPoint:CGPointMake(10, 50)];// 连线
[rectanglePath addLineToPoint:CGPointMake(50, 50)];// 连线
[rectanglePath addLineToPoint:CGPointMake(50, 10)];// 连线
[rectanglePath closePath];// 这里必须闭合,否则空了一边没有连接

// 创建形状图层
CAShapeLayer *rectangleLayer = [CAShapeLayer layer];
rectangleLayer.path = rectanglePath.CGPath;// 路径为矩形
rectangleLayer.strokeColor = [UIColor redColor].CGColor;// 红色描边
rectangleLayer.lineWidth = 6.0;// 线宽
rectangleLayer.fillColor = [UIColor yellowColor].CGColor;// 黄色填充
rectangleLayer.frame = CGRectMake(120, 0, 60, 60);
[self.layer addSublayer:rectangleLayer];

g、圆环
❶ 计算圆心和半径
// 取最小一边的一半作为半径
CGSize size = self.bounds.size;
CGFloat radius = 0;
if (size.width >= size.height)
{
    radius = size.height / 2;
}
else
{
    radius = size.width / 2;
}

// 圆点
CGPoint center = CGPointMake(size.width/2, size.height/2);
❷ 绘制上半圆环
// 创建顶部路径
UIBezierPath *topMaskPath = [UIBezierPath bezierPath];
[topMaskPath moveToPoint:CGPointMake(0, center.y)];// 起始点
[topMaskPath addArcWithCenter:center radius:radius startAngle:M_PI endAngle:M_PI*2 clockwise:YES];// 半圆
[topMaskPath addLineToPoint:CGPointMake(center.x + radius - 20, center.y)];// 添加外环和内环的连接线
[topMaskPath addArcWithCenter:center radius:radius-20 startAngle:M_PI*2 endAngle:M_PI clockwise:NO];// 半圆
[topMaskPath closePath];// 闭合

// 创建顶部形状图层作为mask
CAShapeLayer *topMaskLayer = [CAShapeLayer layer];
topMaskLayer.path = topMaskPath.CGPath;// 路径


// 创建顶部图层
CALayer *topLayer = [CALayer layer];
topLayer.backgroundColor = [UIColor redColor].CGColor;// 红色背景
topLayer.mask = topMaskLayer;// 形状图层作为mask
topLayer.frame = self.bounds;
[self.layer addSublayer:topLayer];
❸ 绘制下半圆环
// 创建底部路径
UIBezierPath *bottomMaskPath = [UIBezierPath bezierPath];
[bottomMaskPath moveToPoint:CGPointMake(0, center.y)];// 起始点
[bottomMaskPath addArcWithCenter:center radius:radius startAngle:M_PI endAngle:0 clockwise:NO];// 半圆
[bottomMaskPath addLineToPoint:CGPointMake(center.x+radius-20, center.y)];// 添加外环和内环的连接线
[bottomMaskPath addArcWithCenter:center radius:radius-20 startAngle:0 endAngle:M_PI clockwise:YES];// 半圆
[bottomMaskPath closePath];// 闭合

// 创建底部形状图层作为mask
CAShapeLayer *bottomMaskLayer = [CAShapeLayer layer];
bottomMaskLayer.path = bottomMaskPath.CGPath;// 路径

// 创建底部图层
CALayer *bottomLayer = [CALayer layer];
bottomLayer.backgroundColor = [UIColor redColor].CGColor;// 红色背景
bottomLayer.mask = bottomMaskLayer;// 形状图层作为mask
bottomLayer.frame = self.bounds;
[self.layer addSublayer:bottomLayer];

h、通过CAShapeLayer方式绘制虚线
虚线
- (void)drawDashLine:(UIView *)lineView lineLength:(int)lineLength lineSpacing:(int)lineSpacing lineColor:(UIColor *)lineColor
{
    CAShapeLayer *shapeLayer = [CAShapeLayer layer];
    [shapeLayer setBounds:lineView.bounds];// 大小
    [shapeLayer setPosition:CGPointMake(CGRectGetWidth(lineView.frame) / 2, CGRectGetHeight(lineView.frame))];// 位置
    [shapeLayer setFillColor:[UIColor clearColor].CGColor];// 填充颜色
    
    // 设置虚线颜色
    [shapeLayer setStrokeColor:lineColor.CGColor];
    // 设置虚线宽度(其实是高度)
    [shapeLayer setLineWidth:CGRectGetHeight(lineView.frame)];
    [shapeLayer setLineJoin:kCALineJoinRound];
    // 设置线宽,线间距
    [shapeLayer setLineDashPattern:[NSArray arrayWithObjects:[NSNumber numberWithInt:lineLength], [NSNumber numberWithInt:lineSpacing], nil]];
    
    // 设置路径
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathMoveToPoint(path, NULL, 0, 0);// 起点
    CGPathAddLineToPoint(path, NULL,CGRectGetWidth(lineView.frame), 0);// 终点
    [shapeLayer setPath:path];
    CGPathRelease(path);
    
    // 把绘制好的虚线添加上来
    [lineView.layer addSublayer:shapeLayer];
}

2、CAGradientLayer

a、CAGradientLayer的坐标系统
  • CAGradientLayer的坐标系统是从(0,0)(1,1)绘制的矩形
  • CAGradientLayerframe值的size不为正方形的话,坐标系统会被拉伸
  • CAGradientLayerstartPointendPoint会直接决定颜色的绘制方向
  • CAGradientLayer的颜色分割点时以0到1的比例来计算的

b、属性
  • CALayer的所有属性:因为CAGradientLayer继承自CALayer,所以CAGradientLayer可以使用其父类CALayer所有开放的属性
  • colors:设置渐变的颜色,这是个数组,可以添加多个颜色,颜色属于CGColorRef类,所以必须加CGColor,而且前面还要加(id)
gradient.colors = @[(id)[UIColor blackColor].CGColor,(id)[UIColor whiteColor].CGColor];

locations:渐变颜色的区间分布,locations的数组长度和color一致也可以不一致,这个值一般不用管它,默认是nil,会平均分布
startPoint和endPoint:startPointendPoint分别为渐变的起始方向与结束方向,它是以矩形的四个角为基础的,(0,0)为左上角、(1,0)为右上角、(0,1)为左下角、(1,1)为右下角,默认是值是(0.5,0)(0.5,1)

//渐变从左下角开始
gradient.startPoint = CGPointMake(0, 1);
//渐变到右上角结束
gradient.endPoint = CGPointMake(1, 0);

type:默认值是kCAGradientLayerAxial,表示按像素均匀变化。除了默认值也无其它选项。


c、演示
❶ 从左到右红蓝渐变
CAGradientLayer *leftToRightGradientLayer = [CAGradientLayer layer];
leftToRightGradientLayer.colors = @[(id)[UIColor redColor].CGColor, (id)[UIColor blueColor].CGColor];// 红蓝
leftToRightGradientLayer.startPoint = CGPointMake(0, 0.5);// 开始位置
leftToRightGradientLayer.endPoint = CGPointMake(1, 0.5);// 结束位置
leftToRightGradientLayer.frame = CGRectMake(0, 0, 100, 100);
[self.layer addSublayer:leftToRightGradientLayer];
❷ 多色对角线渐变
CAGradientLayer *multiGradientLayer = [CAGradientLayer layer];
multiGradientLayer.colors = @[(id)[UIColor redColor].CGColor, (id)[UIColor blueColor].CGColor, (id)[UIColor yellowColor].CGColor];// 红蓝黄
multiGradientLayer.locations = @[@0, @0.5, @1];// 控制渐变发生的位置
multiGradientLayer.startPoint = CGPointMake(0, 0);// 开始点
multiGradientLayer.endPoint = CGPointMake(1, 1);// 结束点
multiGradientLayer.frame = CGRectMake(110, 0, 100, 100);
[self.layer addSublayer:multiGradientLayer];
❸ 以圆心做渐变
CAGradientLayer *radialGradientLayer = [CAGradientLayer layer];
radialGradientLayer.colors = @[(id)[UIColor redColor].CGColor, (id)[UIColor blueColor].CGColor];// 红蓝
radialGradientLayer.type = kCAGradientLayerRadial;// 以圆心做渐变
radialGradientLayer.frame = CGRectMake(220, 0, 100, 100);
radialGradientLayer.startPoint = CGPointMake(.5, .5);
radialGradientLayer.endPoint = CGPointMake(.0, 1);
[self.layer addSublayer:radialGradientLayer];

Demo

Demo在我的Github上,欢迎下载。
BasicsDemo

参考文献

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容