1、Quartz 2D 简介

  • Quartz 2D 属于 Core Graphics(所以大多数相关方法的都是以 CG 开头),是 iOS/Mac OSX 提供的在内核之上的强大的 2D 绘图引擎,并且这个绘图引擎是设备无关的。也就是说,不用关心设备的大小,设备的分辨率,只要利用 Quartz 2D,这些设备相关的会自动处理。

  • 1、Quartz 2D 在 iOS 开发中的价值

    • 绘制一些系统 UIKit 框架中不好展示的内容,例如饼图
    • 自定义一些控件
    • 不添加 UI 控件的情况下,使 UI 内容更丰富
    • 绘制图形:线条\三角形\矩形\圆\弧等
    • 绘制文字
    • 绘制\生成图片(图像)
    • 读取\生成 PDF
    • 截图\裁剪图片
    • 自定义 UI 控件
    • iOS 中,大部分控件都是 Quartz 2D 绘制出来的
  • 2、Quartz 2D 提供的强大功能

    • 透明层(transparency layers)
    • 阴影
    • 基于 path 的绘图(path-based drawing)
    • 离屏渲染(offscreen rendering)
    • 复杂的颜色处理(advanced color management)
    • 抗锯齿渲染(anti-aliased rendering)
    • PDF 创建,展示,解析
    • 配合 Core Animation, OpenGL ES, UIKit 完成复杂的功能
  • 3、画板/图形上下文

    • 既然提到绘图,那自然有一个容器来包含绘制的结果,然后把这个结果渲染到屏幕上去,而 Quartz 2D 的容器就是 CGContextRef 数据模型,这种数据模型是 C 的结构体,存储了渲染到屏幕上需要的一切信息。

    • 图形上下文就相当于画布,不同类型的画布就是决定着画得内容将展示在哪里。Quartz 2D 提供了以下几种类型的 Graphics Context

      • Bitmap Graphics Context:位图上下文,在这个上下文上绘制或者渲染的内容,可以获取成图片(需要主动创建一个位图上下文来使用,使用完毕,一定要销毁)。
      • PDF Graphics Context
      • Window Graphics Context
      • Layer Graphics Context:图层上下文,针对 UI 控件的上下文。
      • Printer Graphics Context
  • 4、绘制模型

    • Quartz 2D 采用 painter’s model,意味着每一次绘制都是一层,然后按照顺序一层层的叠加到画板上。

  • 5、数据类型

    • Quartz 2D 中的数据类型都是透明的,也就是说用户只需要使用即可,不需要实际访问其中的变量。Quartz 2D 的 API 是纯 C 语言的,来自于 Core Graphics 框架,数据类型和函数基本都以 CG 作为前缀。

      • CGPathRef :路径类型,用来绘制路径(注意带有 ref 后缀的一般都是绘制的画板)
      • CGImageRef:绘制 bitmap
      • CGLayerRef:绘制 layer,layer 可复用,可离屏渲染
      • CGPatternRef :重复绘制
      • CGFunctionRef:定义回调函数,CGShadingRef 和 CGGradientRef 的辅助类型
      • CGShadingRef 和 CGGradientRef:绘制渐变(例如颜色渐变)
      • CGColorRef 和 CGColorSpaceRef:定义如何处理颜色
      • CGFontRef:绘制文字
      • 其他类型
  • 6、绘制状态

    • 在使用 Quartz 2D 进行绘图的时候,经常需要设置颜色、字体,设置 context 的坐标原点变换,context 旋转。这些影响的都是当前绘制状态。Context 中利用堆栈的方式来保存绘制状态。调用 CGContextSaveGState 来保存当前绘制状态的 copy 到堆栈中,利用 CGContextRestoreGState 弹出堆栈最顶层的绘制状态,设置为当前的绘制状态。注意,不是所有的参数都会保存,以下表格中的参数会保存。

  • 7、Quartz 2D 的内存管理

    • 使用含有 “Create” 或 “Copy” 的函数创建的对象,使用完后必须释放,否则将导致内存泄露。使用不含有 “Create” 或 “Copy” 的函数获取的对象,则不需要释放。

    • 如果 retain 了一个对象,不再使用时,需要将其 release 掉,可以使用 Quartz 2D 的函数来指定 retain 和 release 一个对象。例如,如果创建了一个 CGColorSpace 对象,则使用函数CGColorSpaceRetain 和 CGColorSpaceRelease 来 retain 和 release 对象。

    • 也可以使用 Core Foundation 的 CFRetain 和 CFRelease。注意不能传递 NULL 值给这些函数。

  • 8、drawRect 方法

    • 因为在 drawRect: 方法中才能取得跟 view 相关联的图形上下文,才让我们可以在 drawRect: 方法中绘制。注意:在其他地方拿不到 view 相关的上下文,所以不能实现绘制。

    • 在 drawRect: 方法中取得上下文后,就可以绘制东西到 view 上。View 内部有个 layer(图层)属性,drawRect: 方法中取得的是一个 Layer Graphics Context,因此,绘制的东西其实是绘制到 view 的 layer 上去了。View 之所以能显示东西,完全是因为它内部的 layer。

    • drawRect: 方法的调用

      • 当 view 第一次显示到屏幕上(被加到 UIWindow 上显示出来)时,系统会创建好一个跟当前 view 相关的 Layer 上下文。
      • 系统会通过此上下文,在 drawRect: 方法中绘制好当前 view 的内容。
      • 主动让 view 重绘内容的时候,调用 setNeedsDisplay 或者 setNeedsDisplayInRect: 方法。我们主动调用 drawRect: 方法是无效的。
      • 调用 view 的 setNeedsDisplay 或者 setNeedsDisplayInRect: 方法后,屏幕并不是立即刷新,而是会在下一次刷新屏幕的时候把绘制的内容显示出来。
  • 9、绘图的核心步骤

    • 获得上下文。

    • 绘制/拼接绘图路径。

    • 将路径添加到上下文。

    • 渲染上下文。

    • 所有的绘图,都是这个步骤,即使使用贝塞尔路径,也只是对这个步骤进行了封装。对于绘图而言,拿到上下文很关键。

  • 10、自定义 view

    • 如何利用 Quartz 2D 绘制东西到 view 上

      • 首先,得有图形上下文,因为它能保存绘图信息,并且决定着绘制到什么地方去。
      • 其次,那个图形上下文必须跟view相关联,才能将内容绘制到 view 上面。
    • 自定义 view 的步骤

      • 新建一个类,继承自 UIView。
      • 实现 - (void)drawRect:(CGRect)rect 方法,然后在这个方法中。
      • 取得跟当前 view 相关联的图形上下文。
      • 绘制相应的图形内容。
      • 利用图形上下文将绘制的所有内容渲染显示到 view 上面。

2、Quartz 2D 基本设置

2.1 Quartz 2D 坐标系

  • 和 UIKit 的坐标系不一样,Quartz 2D 的坐标系是在左下角的。Quartz 2D 利用坐标系的旋转位移等操作来绘制复杂的动画。

  • 但是有两个地方的坐标系是正常的 UIKit 坐标系

    • UIView 的 context
    • 通过这个方法 UIGraphicsBeginImageContextWithOptions 返回的 context。
  • Quartz 2D 中的圆形坐标

2.2 Stroke 描边

  • 影响描边的因素

    • 线的宽度 - CGContextSetLineWidth
    • 交叉线的处理方式 - CGContextSetLineJoin
    • 线顶端的处理方式 - CGContextSetLineCap
    • 进一步限制交叉线的处理方式 - CGContextSetMiterLimit
    • 是否要虚线 - Line dash pattern
    • 颜色控件 - CGContextSetStrokeColorSpace
    • 画笔颜色 - CGContextSetStrokeColor/CGContextSetStrokeColorWithColor
    • 描边模式 - CGContextSetStrokePattern
  • CGContextSetMiterLimit

    • 如果当前交叉线绘图模式是 kCGLineJoinMiter(CGContextSetLineJoin),Quartz 2D 根据设置的 miter 值来判断线的 join 是 bevel 或者 miter。具体的模式是:将 miter 的长度除以线的宽度,如果小于设置的 mitetLimit 值,则 join style 为 bevel。

      1. - (void)drawRect:(CGRect)rect {
      2. CGContextRef ctx = UIGraphicsGetCurrentContext();
      3. CGContextMoveToPoint(ctx, 10, 10);
      4. CGContextAddLineToPoint(ctx, 50, 50);
      5. CGContextAddLineToPoint(ctx, 10, 90);
      6. CGContextSetLineWidth(ctx, 10.0);
      7. CGContextSetLineJoin(ctx, kCGLineJoinMiter);
      8. CGContextSetMiterLimit(ctx, 10.0);
      9. CGContextStrokePath(ctx);
      10. }
    • 效果,将 Miter 设置为 1,则效果如下

2.3 Fill 填充

  • Quartz 2D 填充的时候会认为 subpath 是封闭的,然后根据规则来填充。有两种规则

    • nonzero winding number rule:沿着当前点,画一条直线到区域外,检查交叉点,如果交叉点从左到右,则加一,从右到左,则减去一。如果结果不为 0,则绘制。可参见这个 link
    • even-odd rule:沿着当前点,花一条线到区域外,然后检查相交的路径,偶数则绘制,奇数则不绘制。

  • 相关函数

    • CGContextEOFillPath:用 even-odd rule 来填充
    • CGContextFillPath :用 nonzero winding number rule 方式填充
    • CGContextFillRect/CGContextFillRects:填充指定矩形区域内 path
    • CGContextFillEllipseInRect:填充椭圆
    • CGContextDrawPath :绘制当前 path(根据参数 stroke/fill)

2.4 Clip 切割/遮盖

  • 顾名思义,根据 path 只绘制指定的区域,在区域外的都不会绘制。

  • 相关函数

    • CGContextClip :按照 nonzero winding number rule 规则切割
    • CGContextEOClip:按照 even-odd 规则切割
    • CGContextClipToRect :切割到指定矩形
    • CGContextClipToRects:切割到指定矩形组
    • CGContextClipToMask :切割到 mask
  • 举个例子,截取圆形区域。

    1. - (void)drawRect:(CGRect)rect {
    2. CGContextRef ctx = UIGraphicsGetCurrentContext();
    3. CGContextAddArc(ctx, 125, 125, 100, 0, M_PI * 2, true);
    4. CGContextSetFillColorWithColor(ctx, [UIColor lightGrayColor].CGColor);
    5. // 按指定的路径切割,要放在区域填充之前(下一句之前)
    6. CGContextClip(ctx);
    7. CGContextFillRect(ctx, rect);
    8. // 上面两句相当于这一句
    9. // CGContextFillPath(ctx);
    10. }
  • 效果,切割前后

2.5 Subpath 子路径

  • 很简单,在 stroke/fill 或者 CGContextBeginPath/CGContextClosePath 以后就新开启一个子路径。注意 CGContextClosePath,会连接第一个点和最后一个点。

    1. - (void)drawRect:(CGRect)rect {
    2. CGContextRef ctx = UIGraphicsGetCurrentContext();
    3. CGContextBeginPath(ctx);
    4. CGContextAddArc(ctx, 125, 125, 100, 0, M_PI * 2, true);
    5. CGContextSetFillColorWithColor(ctx, [UIColor lightGrayColor].CGColor);
    6. CGContextClosePath(ctx);
    7. CGContextFillPath(ctx);
    8. }

2.6 Blend 混合模式

  • Quartz 2D 中,默认的颜色混合模式采用如下公式

    1. result = (alpha * foreground) + (1 - alpha) * background
  • 可以使用 CGContextSetBlendMode 来设置不同的颜色混合模式,注意设置 blend 是与 context 绘制状态相关的,一切与状态相关的设置都要想到状态堆栈。

  • 官方文档里的例子,blend 模式较多,具体参见官方文档

    • background 和 foreGround

    • Normal Blend Mode 效果

    • Multiply Blend Mode 效果,交叉部分会显得比较暗,用上一层和底层相乘,至少和一层一样暗。

    • Screen Blend Mode 效果,交叉部分比较亮,上层的 reverse 和下层的 reverse 相乘,至少和一个一样亮。

2.7 CTM 状态矩阵

  • Quartz 2D 默认采用设备无关的 user space 来进行绘图,当 context(画板)建立之后,默认的坐标系原点以及方向也就确认了,可以通过 CTM(current transformation matrix)来修改坐标系的原点。从数组图像处理的角度来说,就是对当前 context state 乘以一个状态矩阵。其中的矩阵运算开发者可以不了解。

    • 最初的状态

      1. - (void)drawRect:(CGRect)rect {
      2. CGContextRef context = UIGraphicsGetCurrentContext();
      3. CGContextAddRect(context, CGRectMake(50, 50, 100, 50));
      4. CGContextSetFillColorWithColor(context,[UIColor blueColor].CGColor);
      5. CGContextFillPath(context);
      6. }

  • Translate 平移

    • 在绘制之前,进行坐标系移动,代码中,我们是还是在(50,50)点绘制,但是要注意,当前坐标系的原点已经移了。

      1. - (void)drawRect:(CGRect)rect {
      2. CGContextRef context = UIGraphicsGetCurrentContext();
      3. // Translate
      4. CGContextTranslateCTM(context, 50, 50);
      5. CGContextAddRect(context, CGRectMake(50, 50, 100, 50));
      6. CGContextSetFillColorWithColor(context, [UIColor blueColor].CGColor);
      7. CGContextFillPath(context);
      8. }

  • Rotate 旋转

    • 在 Transform 的基础上我们再 Rotate 45 度,注意 CGContextRotateCTM 传入的参数是弧度。

      1. - (void)drawRect:(CGRect)rect {
      2. CGContextRef context = UIGraphicsGetCurrentContext();
      3. // Translate
      4. CGContextTranslateCTM(context, 50, 50);
      5. // Rotate
      6. CGContextRotateCTM(context, M_PI_4);
      7. CGContextAddRect(context, CGRectMake(50, 50, 100, 50));
      8. CGContextSetFillColorWithColor(context, [UIColor blueColor].CGColor);
      9. CGContextFillPath(context);
      10. }

  • Scale 缩放

    • 对于 Scale 相对来说,好理解一点,无非就是成比例放大缩小。

      1. - (void)drawRect:(CGRect)rect {
      2. CGContextRef context = UIGraphicsGetCurrentContext();
      3. // Translate
      4. CGContextTranslateCTM(context, 50, 50);
      5. // Rotate
      6. CGContextRotateCTM(context, M_PI_4);
      7. // Scale
      8. CGContextScaleCTM(context, 0.5, 0.5);
      9. CGContextAddRect(context, CGRectMake(50, 50, 100, 50));
      10. CGContextSetFillColorWithColor(context, [UIColor blueColor].CGColor);
      11. CGContextFillPath(context);
      12. }

  • Affine Transforms

    • 可以通过以下方法先创建放射矩阵,然后然后再把放射矩阵映射到 CTM。

      • CGAffineTransform
      • CGAffineTransformTranslate
      • CGAffineTransformMakeRotation
      • CGAffineTransformRotate
      • CGAffineTransformMakeScale
      • CGAffineTransformScale

2.8 GState 状态保存恢复

  • 在复杂的绘图中,我们可能只是想对一个 subpath 设置,如进行旋转、移动和缩放等,这时候状态堆栈就起到作用了。

    1. - (void)drawRect:(CGRect)rect {
    2. CGContextRef context = UIGraphicsGetCurrentContext();
    3. // 保存状态,入栈
    4. CGContextSaveGState(context);
    5. CGContextTranslateCTM(context, 50, 50);
    6. CGContextRotateCTM(context, M_PI_4);
    7. CGContextScaleCTM(context, 0.5, 0.5);
    8. CGContextAddRect(context, CGRectMake(50, 50, 100, 50));
    9. CGContextSetFillColorWithColor(context, [UIColor blueColor].CGColor);
    10. CGContextFillPath(context);
    11. // 恢复,推出栈顶部状态
    12. CGContextRestoreGState(context);
    13. // 这里坐标系已经回到了最开始的状态
    14. CGContextAddRect(context, CGRectMake(0, 0, 50, 50));
    15. CGContextFillPath(context);
    16. }
    1. - (void)drawRect:(CGRect)rect {
    2. // 描述第一条路径
    3. UIBezierPath *path = [UIBezierPath bezierPath];
    4. [path moveToPoint:CGPointMake(10, 125)];
    5. [path addLineToPoint:CGPointMake(240, 125)];
    6. // 获取上下文,保存上下文状态
    7. CGContextRef ctx = UIGraphicsGetCurrentContext();
    8. CGContextSaveGState(ctx);
    9. // 设置属性绘制路径
    10. path.lineWidth = 10;
    11. [[UIColor redColor] set];
    12. [path stroke];
    13. // 描述第二条路径
    14. path = [UIBezierPath bezierPath];
    15. [path moveToPoint:CGPointMake(125, 10)];
    16. [path addLineToPoint:CGPointMake(125, 240)];
    17. // 还原上下文状态
    18. CGContextRestoreGState(ctx);
    19. // 绘制路径
    20. [path stroke];
    21. }
    • 效果

2.9 Shadow 阴影

  • shadow(阴影)的目的是为了使 UI 更具有立体感。注意 Shadow 也是绘制状态相关的,意味着如果仅仅要绘制一个 subpath 的 shadow,要注意 save 和 restore 状态。

  • shadow 主要有三个影响因素,其中不同的 blur 效果如图。

    • x off-set 决定阴影沿着 x 的偏移量
    • y off-set 决定阴影沿着 y 的偏移量
    • blur value 决定了阴影的边缘区域是不是模糊的

  • 相关函数

    • CGContextSetShadow

    • CGContextSetShadowWithColor:唯一区别是设置了阴影颜色

    • 参数

      • context:绘制画板
      • offset :阴影偏移量,参考 context 的坐标系
      • blur :非负数,决定阴影的模糊程度
  • 设置阴影

    1. - (void)drawRect:(CGRect)rect {
    2. CGContextRef context = UIGraphicsGetCurrentContext();
    3. CGContextAddArc(context, 50, 50, 100, 0, M_PI_2, 0);
    4. CGContextSetLineCap(context, kCGLineCapRound);
    5. CGContextSetLineWidth(context, 10.0);
    6. // 设置阴影
    7. CGContextSetShadow(context, CGSizeMake(15.0, 15.0), 1.0);
    8. // CGContextSetShadowWithColor(context, CGSizeMake(15.0, 15.0), 8.0, [UIColor redColor].CGColor);
    9. CGContextStrokePath(context);
    10. }
    • 效果

2.10 Gradient 渐变

  • 渐变无非就是从一种颜色逐渐变换到另一种颜色,Quartz 2D 提供了两种渐变模型。通过这两种渐变的嵌套使用,Quartz 2D 能够绘制出非常漂亮的图形。

    • axial gradient:线性渐变,使用的时候设置好两个顶点的颜色,也可以设置中间过渡色。

    • radial gradient:圆形渐变,这种模式的渐变允许一个圆到另一个圆的渐变,一个点到一个圆的渐变。

  • 可以对渐变结束或者开始的额外区域使用指定颜色填充。

  • 渐变的两种绘制模型

    • CGGradient:使用这种数据类型只需要制定两个顶点的颜色,以及绘制模式,其余的 Quartz 2D 会给绘制,但是渐变的数学模型不灵活。

    • CGShading :使用这种数据类型需要自己定义 CFFunction 来计算每一个点的渐变颜色,较为复杂,但是能够更灵活的绘制。

    • 1、CGGradient 绘制

      • 创建一个 CGGradient 对象,指定颜色域(一般就是 RGB),指定颜色变化的数组,指定对应颜色位置的数组,指定每个数组数据的个数。

      • 用 CGContextDrawLinearGradient 或者 CGContextDrawRadialGradient 绘制。

      • 释放 CGGradient 对象。

      • CGGradientCreateWithColorComponents 函数

        1. CGGradientRef __nullable CGGradientCreateWithColorComponents(CGColorSpaceRef cg_nullable space,
        2. const CGFloat * cg_nullable components,
        3. const CGFloat * __nullable locations,
        4. size_t count)
        5. 参数:
        6. space :颜色域
        7. components:颜色变化的数组
        8. locations :对应颜色位置的数组
        9. count :每个数组数据的个数
      • 线性渐变

        1. - (void)drawRect:(CGRect)rect {
        2. CGContextRef context = UIGraphicsGetCurrentContext();
        3. // 设置渐变
        4. CGColorSpaceRef deviceRGB = CGColorSpaceCreateDeviceRGB();
        5. CGFloat components[8] = {1.0, 0.0, 0.0, 1.0, // 红色
        6. 0.0, 1.0, 0.0, // 绿色
        7. 1.0};
        8. CGFloat locations[2] = {0.0, 1.0};
        9. size_t num_of_locations = 2;
        10. CGGradientRef gradient = CGGradientCreateWithColorComponents(deviceRGB,
        11. components,
        12. locations,
        13. num_of_locations);
        14. // 渐变开始结束点位置
        15. CGPoint startPoint = CGPointMake(0, 0);
        16. CGPoint endPoint = CGPointMake(250, 250);
        17. // 创建线性渐变
        18. CGContextDrawLinearGradient(context,
        19. gradient,
        20. startPoint,
        21. endPoint,
        22. 0);
        23. CGColorSpaceRelease(deviceRGB);
        24. CGGradientRelease(gradient);
        25. }
        • 效果

      • 圆形渐变

        1. - (void)drawRect:(CGRect)rect {
        2. CGContextRef context = UIGraphicsGetCurrentContext();
        3. // 设置渐变
        4. CGColorSpaceRef deviceRGB = CGColorSpaceCreateDeviceRGB();
        5. CGFloat components[8] = {1.0, 0.0, 0.0, 1.0, // 红色
        6. 0.0, 1.0, 0.0, // 绿色
        7. 1.0};
        8. CGFloat locations[2] = {0.0, 1.0};
        9. size_t num_of_locations = 2;
        10. CGGradientRef gradient = CGGradientCreateWithColorComponents(deviceRGB,
        11. components,
        12. locations,
        13. num_of_locations);
        14. // 渐变开始结束圆心位置
        15. CGPoint startCenter = CGPointMake(80, 80);
        16. CGPoint endCenter = CGPointMake(120, 120);
        17. // 渐变开始结束半径
        18. CGFloat startRadius = 0.0;
        19. CGFloat endRadius = 100.0;
        20. // 创建圆形渐变
        21. CGContextDrawRadialGradient(context,
        22. gradient,
        23. startCenter,
        24. startRadius,
        25. endCenter,
        26. endRadius,
        27. 0);
        28. CGColorSpaceRelease(deviceRGB);
        29. CGGradientRelease(gradient);
        30. }
        • 效果

2.11 Bitmap 位图

  • Bitmap 叫做位图,每一个像素点由 1-32bit 组成。每个像素点包括多个颜色组件和一个 Alpha 组件(例如:RGBA)。

  • iOS 中指出如下格式的图片 JPEG, GIF, PNG, TIF, ICO, GMP, XBM 和 CUR。其他格式的图片要给 Quartz 2D 传入图片的数据分布信息。

  • 数据类型 CGImageRef,在 Quartz 2D 中,Bitmap 的数据由 CGImageRef 封装。由以下几个函数可以创建 CGImageRef 对象

    • CGImageCreate:最灵活,但也是最复杂的一种方式,要传入 11 个参数。

    • CGImageSourceCreate:ImageAtIndex:通过已经存在的 Image 对象来创建

    • CGImageSourceCreate:ThumbnailAtIndex:和上一个函数类似,不过这个是创建缩略图

    • CGBitmapContextCreateImage:通过 Copy Bitmap Graphics 来创建

    • CGImageCreateWith:ImageInRect:通过在某一个矩形内数据来创建

    • 函数 CGImageCreate

      1. CGImageRef _Nullable CGImageCreate(size_t width,
      2. size_t height,
      3. size_t bitsPerComponent,
      4. size_t bitsPerPixel,
      5. size_t bytesPerRow,
      6. CGColorSpaceRef _Nullable space,
      7. CGBitmapInfo bitmapInfo,
      8. CGDataProviderRef _Nullable provider,
      9. const CGFloat * _Nullable decode,
      10. bool shouldInterpolate,
      11. CGColorRenderingIntent intent);
      12. 参数:
      13. width/height :图片的像素宽度,高度
      14. bitsPerComponent:每个 component 的占用 bit 个数,和上文提到的一样
      15. bitsPerPixel :每个像素点占用的 bit 个数。例如 32bit RGBA 中,就是 32
      16. bytesPerRow :每一行占用的 byte 个数
      17. colorspace :颜色空间
      18. bitmapInfo :和上文提到的那个函数一样
      19. provider bitmap 的数据源
      20. decode :解码 array,传入 null,则保持原始数据
      21. interpolation :是否要像素差值来平滑图像
      22. intent :指定了从一个颜色空间 map 到另一个颜色空间的方式
    • 函数 CGBitmapContextCreate

      1. CGContextRef _Nullable CGBitmapContextCreate(void * _Nullable data,
      2. size_t width,
      3. size_t height,
      4. size_t bitsPerComponent,
      5. size_t bytesPerRow,
      6. CGColorSpaceRef _Nullable space,
      7. uint32_t bitmapInfo);
      8. 参数:
      9. data :是一个指针,指向存储绘制的 bitmap context 的实际数据的地址,最少大小为 bytesPerRow * height。可以传入 null,让 Quartz 自动分配计算
      10. width, heightbitmap 的宽度,高度,以像素为单位
      11. bytesPerRow :每一行的 byte 数目。如果 data 传入 null,这里传入 0,则会自动计算一个 component 占据多少位。对于 32bit RGBA 空间,则是 88*432
      12. space :颜色空间,一般就是 DeviceRGB
      13. bitmapInfo :一个常量,指定了是否具有 alpha 通道,alpha 通道的位置,像素点存储的数据类型是 float 还是 Integer 等信息
      14. 其中 bitmapInfo 可以传入的参数如下
      15. enum CGImageAlphaInfo {
      16. kCGImageAlphaNone,
      17. kCGImageAlphaPremultipliedLast,
      18. kCGImageAlphaPremultipliedFirst,
      19. kCGImageAlphaLast,
      20. kCGImageAlphaFirst,
      21. kCGImageAlphaNoneSkipLast,
      22. kCGImageAlphaNoneSkipFirst,
      23. kCGImageAlphaOnly
      24. };
  • 1、重绘图片

    • 原图(2560 * 1600)

    • 重新绘制成 250 * 100,并在图片中间加上我们自定义的绘制

      1. - (void)drawRect:(CGRect)rect {
      2. CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
      3. // 图片绘图区域的大小
      4. CGSize targetSize = CGSizeMake(250, 125);
      5. // 获取图形上下文
      6. CGContextRef bitmapCtx = CGBitmapContextCreate(NULL,
      7. targetSize.width,
      8. targetSize.height,
      9. 8,
      10. targetSize.width * 4,
      11. rgb,
      12. kCGImageAlphaPremultipliedFirst);
      13. // 绘制图片
      14. CGRect imageRect;
      15. imageRect.origin = CGPointMake(0, 0); // 设置图片的位置,左下角坐标系
      16. imageRect.size = CGSizeMake(250, 100); // 设置图片的大小
      17. UIImage *imageToDraw = [UIImage imageNamed:@"image.jpg"];
      18. CGContextDrawImage(bitmapCtx, imageRect, imageToDraw.CGImage);
      19. // 绘制自定义图形
      20. CGContextAddArc(bitmapCtx, 100, 40, 20, M_PI_4, M_PI_2, true);
      21. CGContextSetLineWidth(bitmapCtx, 4.0);
      22. CGContextSetStrokeColorWithColor(bitmapCtx, [UIColor redColor].CGColor);
      23. CGContextStrokePath(bitmapCtx);
      24. // 渲染生成 CGImage
      25. CGImageRef imageRef = CGBitmapContextCreateImage(bitmapCtx);
      26. // 转换成 UIImage
      27. UIImage *image = [[UIImage alloc] initWithCGImage:imageRef];
      28. CGImageRelease(imageRef);
      29. CGContextRelease(bitmapCtx);
      30. CGColorSpaceRelease(rgb);
      31. UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
      32. [self addSubview:imageView];
      33. }
      • 效果

  • 2、截取图片

    • 截取图片

      1. - (void)drawRect:(CGRect)rect {
      2. CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
      3. // 图片绘图区域的大小
      4. CGSize targetSize = CGSizeMake(250, 125);
      5. // 获取图形上下文
      6. CGContextRef bitmapCtx = CGBitmapContextCreate(NULL,
      7. targetSize.width,
      8. targetSize.height,
      9. 8,
      10. targetSize.width * 4,
      11. rgb,
      12. kCGImageAlphaPremultipliedFirst);
      13. UIImage *imageToDraw = [UIImage imageNamed:@"image.jpg"];
      14. // 渲染生成 CGImage
      15. CGRect imageRect = CGRectMake(0, 0, 250, 100); // 左上角坐标系,位置设置不起作用
      16. CGImageRef partImageRef = CGImageCreateWithImageInRect(imageToDraw.CGImage, imageRect);
      17. // 转换成 UIImage
      18. UIImage *image = [[UIImage alloc] initWithCGImage:partImageRef];
      19. CGImageRelease(partImageRef);
      20. CGContextRelease(bitmapCtx);
      21. CGColorSpaceRelease(rgb);
      22. UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
      23. [self addSubview:imageView];
      24. }
      • 效果

3、Quartz 2D 常用函数

  • 1、常用拼接路径函数

    1. // 新建一个起点
    2. void CGContextMoveToPoint(CGContextRef c, CGFloat x, CGFloat y)
    3. // 添加新的线段到某个点
    4. void CGContextAddLineToPoint(CGContextRef c, CGFloat x, CGFloat y)
    5. // 封闭路径
    6. void CGContextClosePath(CGContextRef cg_nullable c)
    7. // 添加一个矩形
    8. void CGContextAddRect(CGContextRef c, CGRect rect)
    9. // 添加一个椭圆
    10. void CGContextAddEllipseInRect(CGContextRef context, CGRect rect)
    11. // 添加一个圆弧
    12. void CGContextAddArc(CGContextRef c, CGFloat x, CGFloat y, CGFloat radius, CGFloat startAngle, CGFloat endAngle, int clockwise)
  • 2、常用绘制路径函数

    1. // Mode 参数决定绘制的模式
    2. void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode)
    3. // 绘制空心路径
    4. void CGContextStrokePath(CGContextRef c)
    5. // 绘制实心路径
    6. void CGContextFillPath(CGContextRef c)
    • 一般以 CGContextDraw、CGContextStroke、CGContextFill 开头的函数,都是用来绘制路径的。
  • 3、图形上下文栈的操作函数

    1. // 将当前的上下文 Copy 一份,保存到栈顶(那个栈叫做 “图形上下文栈”)
    2. void CGContextSaveGState(CGContextRef c)
    3. // 将栈顶的上下文出栈,替换掉当前的上下文
    4. void CGContextRestoreGState(CGContextRef c)
  • 4、矩阵操作函数

    1. // 缩放
    2. void CGContextScaleCTM(CGContextRef c, CGFloat sx, CGFloat sy)
    3. // 旋转
    4. void CGContextRotateCTM(CGContextRef c, CGFloat angle)
    5. // 平移
    6. void CGContextTranslateCTM(CGContextRef c, CGFloat tx, CGFloat ty)
    • 利用矩阵操作,能让绘制到上下文中的所有路径一起发生变化。

4、贝塞尔路径

  • 贝塞尔路径(UIBezierPath)是 UIKit 框架中对 Quartz 2D 绘图的封装。实际操作起来,使用贝塞尔路径,更为方便。用法与 CGContextRef 类似,但是 OC 对其进行了封装,更加面向对象。

  • 具体讲解见 Quartz 2D 贝塞尔曲线

  • 二阶贝塞尔曲线示意图

  • 三阶贝塞尔曲线示意图

  • 贝塞尔路径常用的方法

    1. // 设置起始点
    2. - (void)moveToPoint:(CGPoint)point;
    3. // 添加直线到一点
    4. - (void)addLineToPoint:(CGPoint)point;
    5. // 封闭闭路径
    6. - (void)closePath;
    7. // 返回一个描述椭圆的路径
    8. + (UIBezierPath *)bezierPathWithOvalInRect:(CGRect)rect;
    9. // 贝塞尔曲线
    10. - (void)addQuadCurveToPoint:(CGPoint)endPoint controlPoint:(CGPoint)controlPoint;
    11. // 三次贝塞尔曲线
    12. - (void)addCurveToPoint:(CGPoint)endPoint controlPoint1:(CGPoint)controlPoint1 controlPoint2:(CGPoint)controlPoint2;
    13. // 绘制圆弧
    14. - (void)addArcWithCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise;

5、基本图形绘制

5.1 绘制直线

  • 1、绘制直线

    • 在 Quartz 2D 中,使用方法 CGContextMoveToPoint 移动画笔到一个点来开始新的子路径,使用 CGContextAddLineToPoint 来从当前开始点添加一条线到结束点,CGContextAddLineToPoint 调用后,此时的终点会重新设置为新的开始点。贝塞尔路径是对 Quartz 2D 绘图的 OC 封装。

    • 方式 1,最原始方式

      1. - (void)drawRect:(CGRect)rect {
      2. // 获取上下文
      3. CGContextRef ctx = UIGraphicsGetCurrentContext();
      4. // 创建路径
      5. CGMutablePathRef path = CGPathCreateMutable();
      6. // 描述路径, 设置起点,path:给哪个路径设置起点
      7. CGPathMoveToPoint(path, NULL, 50, 50);
      8. // 添加一根线到某个点
      9. CGPathAddLineToPoint(path, NULL, 200, 200);
      10. // 把路径添加到上下文
      11. CGContextAddPath(ctx, path);
      12. // 渲染上下文
      13. CGContextStrokePath(ctx);
      14. }
    • 方式 2,简化方式

      1. - (void)drawRect:(CGRect)rect {
      2. // 获取上下文
      3. CGContextRef ctx = UIGraphicsGetCurrentContext();
      4. // 描述路径,设置起点
      5. CGContextMoveToPoint(ctx, 50, 50);
      6. // 添加一根线到某个点
      7. CGContextAddLineToPoint(ctx, 200, 200);
      8. // 渲染上下文
      9. CGContextStrokePath(ctx);
      10. }
    • 方式 3,贝塞尔路径方式

      1. - (void)drawRect:(CGRect)rect {
      2. // 创建路径
      3. UIBezierPath *path = [UIBezierPath bezierPath];
      4. // 设置起点
      5. [path moveToPoint:CGPointMake(50, 50)];
      6. // 添加一根线到某个点
      7. [path addLineToPoint:CGPointMake(200, 200)];
      8. // 绘制路径
      9. [path stroke];
      10. }
    • 方式 4,原始方式和贝塞尔路径方式同时使用

      1. - (void)drawRect:(CGRect)rect {
      2. // 创建路径
      3. UIBezierPath *path = [UIBezierPath bezierPath];
      4. [path moveToPoint:CGPointMake(10, 125)];
      5. [path addLineToPoint:CGPointMake(240, 125)];
      6. // 获取上下文
      7. CGContextRef ctx = UIGraphicsGetCurrentContext();
      8. // 添加路径
      9. CGContextAddPath(ctx, path.CGPath);
      10. // 设置属性
      11. CGContextSetStrokeColorWithColor(ctx, [UIColor redColor].CGColor);
      12. CGContextSetLineWidth(ctx, 5);
      13. // 绘制路径
      14. CGContextStrokePath(ctx);
      15. }
      1. - (void)drawRect:(CGRect)rect {
      2. // 获取上下文,描述路径
      3. CGContextRef ctx = UIGraphicsGetCurrentContext();
      4. CGContextMoveToPoint(ctx, 50, 50);
      5. CGContextAddLineToPoint(ctx, 200, 200);
      6. // 创建贝塞尔路径
      7. UIBezierPath *path = [UIBezierPath bezierPath];
      8. // 添加路径
      9. CGContextAddPath(ctx, path.CGPath);
      10. // 设置属性
      11. [[UIColor redColor] set];
      12. path.lineWidth = 5;
      13. // 绘制路径
      14. [path stroke];
      15. }
    • 效果

  • 2、设置画线状态

    • 线的顶端模式,使用 CGContextSetLineCap 来设置,一共有三种

    • 线的相交模式,使用CGContextSetLineJoin 来设置,一共也有三种

    • 方式 1,原始方式

      1. - (void)drawRect:(CGRect)rect {
      2. // 画线
      3. // 获取上下文
      4. CGContextRef ctx = UIGraphicsGetCurrentContext();
      5. // 描述路径
      6. CGContextMoveToPoint(ctx, 50, 50);
      7. CGContextAddLineToPoint(ctx, 200, 200);
      8. // 画第二条线,默认下一根线的起点就是上一根线终点
      9. // CGContextMoveToPoint(ctx, 200, 50);
      10. CGContextAddLineToPoint(ctx, 50, 225);
      11. // 设置画线状态,一定要放在渲染之前
      12. // 设置颜色
      13. CGContextSetStrokeColorWithColor(ctx, [UIColor redColor].CGColor);
      14. // 设置线宽
      15. CGContextSetLineWidth(ctx, 5);
      16. // 设置相交样式
      17. CGContextSetLineJoin(ctx, kCGLineJoinRound);
      18. // 设置顶端样式
      19. CGContextSetLineCap(ctx, kCGLineCapSquare);
      20. // 渲染
      21. CGContextStrokePath(ctx);
      22. }
    • 方式 2,贝塞尔路径方式

      1. - (void)drawRect:(CGRect)rect {
      2. // 画线
      3. // 创建路径
      4. UIBezierPath *path = [UIBezierPath bezierPath];
      5. // 描述路径
      6. [path moveToPoint:CGPointMake(50, 50)];
      7. [path addLineToPoint:CGPointMake(200, 200)];
      8. // 画第二条线,默认下一根线的起点就是上一根线终点
      9. // [path moveToPoint:CGPointMake(200, 50)];
      10. [path addLineToPoint:CGPointMake(50, 225)];
      11. // 设置画线状态,一定要放在渲染之前
      12. // 设置颜色
      13. [[UIColor redColor] set];
      14. // 设置线宽
      15. path.lineWidth = 5;
      16. // 设置相交样式
      17. path.lineJoinStyle = kCGLineJoinRound;
      18. // 设置顶端样式
      19. path.lineCapStyle = kCGLineCapSquare;
      20. // 渲染
      21. [path stroke];
      22. }
    • 效果

5.2 绘制虚线

  • 绘制虚线

    • 方式 1,原始方式

      1. - (void)drawRect:(CGRect)rect {
      2. // phase :第一个虚线段从哪里开始,例如传入 3,则从第 3 个单位开始
      3. // lengths:一个 C 数组,表示绘制部分和空白部分的分配。例如传入 [2, 2],则绘制 2 个单位,然后空白 2 个单位,以此重复
      4. // count : lengths 的数量
      5. // 获取上下文
      6. CGContextRef ctx = UIGraphicsGetCurrentContext();
      7. // 绘制直线
      8. CGContextMoveToPoint(ctx, 50, 50);
      9. CGContextAddLineToPoint(ctx, 200, 200);
      10. // 设置虚线
      11. CGFloat lengths[] = {5};
      12. CGContextSetLineDash(ctx, 1, lengths, 1);
      13. // 渲染
      14. CGContextStrokePath(ctx);
      15. }
    • 方式 2,贝塞尔路径方式

      1. - (void)drawRect:(CGRect)rect {
      2. // phase :第一个虚线段从哪里开始,例如传入 3,则从第 3 个单位开始
      3. // pattern:一个 C 数组,表示绘制部分和空白部分的分配。例如传入 [2, 2],则绘制 2 个单位,然后空白 2 个单位,以此重复
      4. // count : lengths 的数量
      5. // 创建路径
      6. UIBezierPath *path = [UIBezierPath bezierPath];
      7. // 绘制直线
      8. [path moveToPoint:CGPointMake(50, 50)];
      9. [path addLineToPoint:CGPointMake(200, 200)];
      10. // 设置虚线
      11. CGFloat lengths[] = {5};
      12. [path setLineDash:lengths count:1 phase:1];
      13. // 渲染
      14. [path stroke];
      15. }
    • 效果

5.3 绘制曲线

  • Quartz 2D 使用计算机图形学中的多项式来绘制曲线,支持二次和三次曲线。

  • 1、绘制二次曲线

    • 方式 1,原始方式

      1. - (void)drawRect:(CGRect)rect {
      2. // cpx, cpy:控制点
      3. // x, y :曲线终点
      4. // 获取上下文
      5. CGContextRef ctx = UIGraphicsGetCurrentContext();
      6. // 设置起点
      7. CGContextMoveToPoint(ctx, 50, 200);
      8. // 绘制曲线
      9. CGContextAddQuadCurveToPoint(ctx, 125, 50, 200, 200);
      10. // 绘制空心路径
      11. CGContextStrokePath(ctx);
      12. // 绘制实心路径
      13. // CGContextFillPath(ctx);
      14. }
    • 方式 2,贝塞尔路径方式

      1. - (void)drawRect:(CGRect)rect {
      2. // controlPoint:控制点
      3. // endPoint :曲线终点
      4. // 创建路径
      5. UIBezierPath *path = [UIBezierPath bezierPath];
      6. // 设置起点
      7. [path moveToPoint:CGPointMake(50, 200)];
      8. // 绘制曲线
      9. [path addQuadCurveToPoint:CGPointMake(200, 200) controlPoint:CGPointMake(125, 50)];
      10. // 绘制空心路径
      11. [path stroke];
      12. // 绘制实心路径
      13. // [path fill];
      14. }
    • 效果

  • 2、绘制三次曲线

    • 方式 1,原始方式

      1. - (void)drawRect:(CGRect)rect {
      2. // cp1x, cp1y:控制点 1
      3. // cp2x, cp2y:控制点 2
      4. // x, y :曲线终点
      5. // 获取上下文
      6. CGContextRef ctx = UIGraphicsGetCurrentContext();
      7. // 设置起点
      8. CGContextMoveToPoint(ctx, 50, 100);
      9. // 绘制曲线
      10. CGContextAddCurveToPoint(ctx, 100, 10, 150, 190, 200, 100);
      11. // 绘制空心路径
      12. CGContextStrokePath(ctx);
      13. // 绘制实心路径
      14. // CGContextFillPath(ctx);
      15. }
    • 方式 2,贝塞尔路径方式

      1. - (void)drawRect:(CGRect)rect {
      2. // controlPoint1:控制点 1
      3. // controlPoint2:控制点 2
      4. // endPoint :曲线终点
      5. // 创建路径
      6. UIBezierPath *path = [UIBezierPath bezierPath];
      7. // 设置起点
      8. [path moveToPoint:CGPointMake(50, 100)];
      9. // 绘制曲线
      10. [path addCurveToPoint:CGPointMake(200, 100)
      11. controlPoint1:CGPointMake(100, 10)
      12. controlPoint2:CGPointMake(150, 190)];
      13. // 绘制空心路径
      14. [path stroke];
      15. // 绘制实心路径
      16. // [path fill];
      17. }
    • 效果

  • 3、绘制图形设置

    • 方式 1,原始方式

      1. // 设置空心路径的颜色
      2. CGContextSetStrokeColorWithColor(ctx, [UIColor redColor].CGColor);
      3. // 设置实心路径的填充颜色
      4. CGContextSetFillColorWithColor(ctx, [UIColor redColor].CGColor);
      5. // 绘制空心路径
      6. CGContextStrokePath(ctx);
      7. // 绘制实心路径
      8. CGContextFillPath(ctx);
    • 方式 2,贝塞尔路径方式

      1. // 设置空心路径的颜色
      2. [[UIColor redColor] setStroke];
      3. // 设置实心路径的填充颜色
      4. [[UIColor redColor] setFill];
      5. // 设置空心路径和实心路径的颜色
      6. [[UIColor redColor] set];
      7. // 绘制空心路径
      8. [path stroke];
      9. // 绘制实心路径
      10. [path fill];
    • 效果

5.4 绘制三角形

  • 绘制三角形

    • 方式 1,原始方式

      1. - (void)drawRect:(CGRect)rect {
      2. // 获取上下文
      3. CGContextRef ctx = UIGraphicsGetCurrentContext();
      4. // 描述路径
      5. CGContextMoveToPoint(ctx, 100, 50);
      6. CGContextAddLineToPoint(ctx, 20, 200);
      7. CGContextAddLineToPoint(ctx, 200, 200);
      8. // 封闭路径,自动连接首尾
      9. CGContextClosePath(ctx);
      10. // 渲染
      11. // 绘制空心路径
      12. CGContextStrokePath(ctx);
      13. // 绘制实心路径
      14. // CGContextFillPath(ctx);
      15. }
    • 方式 2,贝塞尔路径方式

      1. - (void)drawRect:(CGRect)rect {
      2. // 创建路径
      3. UIBezierPath *path = [UIBezierPath bezierPath];
      4. // 描述路径
      5. [path moveToPoint:CGPointMake(100, 50)];
      6. [path addLineToPoint:CGPointMake(20, 200)];
      7. [path addLineToPoint:CGPointMake(200, 200)];
      8. // 封闭路径,自动连接首尾
      9. [path closePath];
      10. // 渲染
      11. // 绘制空心路径
      12. [path stroke];
      13. // 绘制实心路径
      14. // [path fill];
      15. }
    • 效果

5.5 绘制矩形

  • 绘制矩形

    • 方式 1,原始方式

      1. - (void)drawRect:(CGRect)rect {
      2. // 获取上下文
      3. CGContextRef ctx = UIGraphicsGetCurrentContext();
      4. // 描述路径
      5. CGContextAddRect(ctx, CGRectMake(20, 50, 200, 100));
      6. // 绘制空心路径
      7. CGContextStrokePath(ctx);
      8. // 绘制实心路径
      9. // CGContextFillPath(ctx);
      10. }
    • 方式 2,贝塞尔路径方式

      1. - (void)drawRect:(CGRect)rect {
      2. // 创建路径,绘制图形
      3. UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(20, 50, 200, 100)];
      4. // 绘制空心路径
      5. [path stroke];
      6. // 绘制实心路径
      7. // [path fill];
      8. }
    • 效果

5.6 绘制圆角矩形

  • 绘制圆角矩形

    • 方式 1,原始方式

      1. - (void)drawRect:(CGRect)rect {
      2. // x1, y1:圆角两个切线的交点
      3. // x2, y2:圆角终点
      4. // radius:圆角半径
      5. // 获取上下文
      6. CGContextRef ctx = UIGraphicsGetCurrentContext();
      7. CGFloat x = 20;
      8. CGFloat y = 50;
      9. CGFloat w = 200;
      10. CGFloat h = 100;
      11. CGFloat r = 20;
      12. CGContextMoveToPoint(ctx, x, y + r);
      13. CGContextAddArcToPoint(ctx, x, y, x + r, y, r); // 左上角
      14. CGContextAddLineToPoint(ctx, x + w - r, y);
      15. CGContextAddArcToPoint(ctx, x + w, y, x + w, y + r, r); // 右上角
      16. CGContextAddLineToPoint(ctx, x + w, y + h - r);
      17. CGContextAddArcToPoint(ctx, x + w, y + h, x + w - r, y + h, r); // 右下角
      18. CGContextAddLineToPoint(ctx, x + r, y + h);
      19. CGContextAddArcToPoint(ctx, x, y + h, x, y + h - r, r); // 左下角
      20. CGContextClosePath(ctx);
      21. // 绘制空心路径
      22. CGContextStrokePath(ctx);
      23. // 绘制实心路径
      24. // CGContextFillPath(ctx);
      25. }
    • 方式 2,贝塞尔路径方式

      1. - (void)drawRect:(CGRect)rect {
      2. // rect :矩形位置尺寸
      3. // cornerRadius:圆角半径
      4. // 创建路径,绘制图形
      5. UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(20, 50, 200, 100)
      6. cornerRadius:20];
      7. // 绘制空心路径
      8. [path stroke];
      9. // 绘制实心路径
      10. // [path fill];
      11. }
    • 效果

5.7 绘制圆弧

  • 绘制圆弧

    • Quartz 2D 提供了两个方法来绘制圆弧

      • CGContextAddArc,普通的圆弧一部分(以某圆心,某半径,某弧度的圆弧)。

      • CGContextAddArcToPoint,用来绘制圆角。

        • 函数体

          1. void CGContextAddArcToPoint(CGContextRef cg_nullable c,
          2. CGFloat x1,
          3. CGFloat y1,
          4. CGFloat x2,
          5. CGFloat y2,
          6. CGFloat radius)
        • 参数

          1. c :图形上下文
          2. x1, y1:和当前点 (x0, y0) 决定了第一条切线(x0, y0)-> (x1, y1)
          3. x2, y2:和 (x1, y1) 决定了第二条切线
          4. radius:相切的半径。
        • 也就是说,绘制一个半径为 radius 的圆弧,和上述两条直线都相切。图中的两条红线就是上文提到的两条线,分别是 (x0,y0) -> (x1,y1) 和 (x1,y1) -> (x2,y2),那么和这两条线都想切的自然就是图中的蓝色圆弧了.

    • 方式 1,原始方式

      1. - (void)drawRect:(CGRect)rect {
      2. // x, y :圆心
      3. // radius :半径
      4. // startAngle:开始弧度
      5. // endAngle :结束弧度
      6. // clockwise :方向,false 顺时针,true 逆时针
      7. // 获取上下文
      8. CGContextRef ctx = UIGraphicsGetCurrentContext();
      9. // 描述路径
      10. CGContextAddArc(ctx, 125, 125, 100, 0, M_PI_2, false);
      11. // 绘制空心路径
      12. CGContextStrokePath(ctx);
      13. // 绘制实心路径
      14. // CGContextFillPath(ctx);
      15. }
      1. - (void)drawRect:(CGRect)rect {
      2. // x1, y1:圆角两个切线的交点
      3. // x2, y2:圆角终点
      4. // radius:圆角半径
      5. // 获取上下文
      6. CGContextRef ctx = UIGraphicsGetCurrentContext();
      7. // 设置起点
      8. CGContextMoveToPoint(ctx, 225, 125);
      9. // 绘制圆弧
      10. CGContextAddArcToPoint(ctx, 225, 225, 125, 225, 100);
      11. // 绘制空心路径
      12. CGContextStrokePath(ctx);
      13. // 绘制实心路径
      14. // CGContextFillPath(ctx);
      15. }
    • 方式 2,贝塞尔路径方式

      1. - (void)drawRect:(CGRect)rect {
      2. // Center :圆心
      3. // radius :半径
      4. // startAngle:开始弧度
      5. // endAngle :结束弧度
      6. // clockwise :方向,YES 顺时针,NO 逆时针
      7. // 创建路径,绘制图形
      8. UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(125, 125)
      9. radius:100
      10. startAngle:0
      11. endAngle:M_PI_2
      12. clockwise:YES];
      13. // 绘制空心路径
      14. [path stroke];
      15. // 绘制实心路径
      16. // [path fill];
      17. }
      1. - (void)drawRect:(CGRect)rect {
      2. // Center :圆心
      3. // radius :半径
      4. // startAngle:开始弧度
      5. // endAngle :结束弧度
      6. // clockwise :方向,YES 顺时针,NO 逆时针
      7. // 创建路径,绘制图形
      8. UIBezierPath *path = [UIBezierPath bezierPath];
      9. // 设置起点
      10. [path moveToPoint:CGPointMake(225, 125)];
      11. // 绘制圆弧
      12. [path addArcWithCenter:CGPointMake(125, 125)
      13. radius:100
      14. startAngle:0
      15. endAngle:M_PI_2
      16. clockwise:YES];
      17. // 绘制空心路径
      18. [path stroke];
      19. // 绘制实心路径
      20. // [path fill];
      21. }
    • 效果

5.8 绘制扇形

  • 绘制扇形

    • 方式 1,原始方式

      1. - (void)drawRect:(CGRect)rect {
      2. // 获取上下文
      3. CGContextRef ctx = UIGraphicsGetCurrentContext();
      4. // 描述路径
      5. CGContextAddArc(ctx, 125, 125, 100, 0, M_PI_4, NO);
      6. // 绘制到圆心的直线
      7. CGContextAddLineToPoint(ctx, 125, 125);
      8. // 封闭路径
      9. CGContextClosePath(ctx);
      10. // 绘制空心路径
      11. CGContextStrokePath(ctx);
      12. // 绘制实心路径
      13. // CGContextFillPath(ctx);
      14. }
    • 方式 2,贝塞尔路径方式

      1. - (void)drawRect:(CGRect)rect {
      2. // 创建路径,绘制图形
      3. UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(125, 125)
      4. radius:100
      5. startAngle:0
      6. endAngle:M_PI_4
      7. clockwise:YES];
      8. // 绘制到圆心的直线
      9. [path addLineToPoint:CGPointMake(125, 125)];
      10. // 封闭路径
      11. [path closePath];
      12. // 绘制空心路径
      13. [path stroke];
      14. // 绘制实心路径
      15. // [path fill];
      16. }
    • 效果

5.9 绘制圆形

  • 绘制圆形

    • 方式 1,原始方式

      1. - (void)drawRect:(CGRect)rect {
      2. // 获取上下文
      3. CGContextRef ctx = UIGraphicsGetCurrentContext();
      4. // 描述路径
      5. CGContextAddArc(ctx, 125, 125, 100, 0, M_PI * 2, NO);
      6. // 绘制空心路径
      7. CGContextStrokePath(ctx);
      8. // 绘制实心路径
      9. // CGContextFillPath(ctx);
      10. }
    • 方式 2,贝塞尔路径方式

      1. - (void)drawRect:(CGRect)rect {
      2. // 创建路径,绘制图形
      3. UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(125, 125)
      4. radius:100
      5. startAngle:0
      6. endAngle:M_PI * 2
      7. clockwise:YES];
      8. // 绘制空心路径
      9. [path stroke];
      10. // 绘制实心路径
      11. // [path fill];
      12. }
    • 效果

5.10 绘制椭圆形

  • 绘制椭圆形

    • 在矩形中设置不同的宽高方式创建。

    • 方式 1,原始方式

      1. - (void)drawRect:(CGRect)rect {
      2. // 获取上下文
      3. CGContextRef ctx = UIGraphicsGetCurrentContext();
      4. // 描述路径
      5. CGContextAddEllipseInRect(ctx, CGRectMake(20, 50, 200, 100));
      6. // 绘制空心路径
      7. CGContextStrokePath(ctx);
      8. // 绘制实心路径
      9. // CGContextFillPath(ctx);
      10. }
    • 方式 2,贝塞尔路径方式

      1. - (void)drawRect:(CGRect)rect {
      2. // 创建路径,绘制图形
      3. UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(20, 50, 200, 100)];
      4. // 绘制空心路径
      5. [path stroke];
      6. // 绘制实心路径
      7. // [path fill];
      8. }
    • 效果

6、统计图绘制

6.1 绘制折线图

  • 绘制折线图

    • 方式 1,原始方式

      1. - (void)drawRect:(CGRect)rect {
      2. CGFloat x1 = 0;
      3. CGFloat x2 = 0;
      4. CGFloat h1 = 0;
      5. CGFloat h2 = 0;
      6. CGFloat y1 = 0;
      7. CGFloat y2 = 0;
      8. CGFloat w = rect.size.width / (self.datas.count - 1);
      9. CGFloat largeNum = [self.datas[0] floatValue];
      10. for (int i = 0; i < self.datas.count; i++) {
      11. if ([self.datas[i] floatValue] > largeNum) {
      12. largeNum = [self.datas[i] floatValue];
      13. }
      14. }
      15. for (int i = 0; i < self.datas.count - 1; i++) {
      16. x1 = w * i;
      17. x2 = w * (i + 1);
      18. h1 = [self.datas[i] floatValue] / (largeNum * 1.2) * rect.size.height;
      19. h2 = [self.datas[i + 1] floatValue] / (largeNum * 1.2) * rect.size.height;
      20. y1 = rect.size.height - h1;
      21. y2 = rect.size.height - h2;
      22. CGContextRef ctx = UIGraphicsGetCurrentContext();
      23. CGContextMoveToPoint(ctx, x1, y1);
      24. CGContextAddLineToPoint(ctx, x2, y2);
      25. CGContextSetStrokeColorWithColor(ctx, self.color.CGColor);
      26. CGContextStrokePath(ctx);
      27. }
      28. }
    • 方式 2,贝塞尔路径方式

      1. - (void)drawRect:(CGRect)rect {
      2. CGFloat x1 = 0;
      3. CGFloat x2 = 0;
      4. CGFloat h1 = 0;
      5. CGFloat h2 = 0;
      6. CGFloat y1 = 0;
      7. CGFloat y2 = 0;
      8. CGFloat w = rect.size.width / (self.datas.count - 1);
      9. CGFloat largeNum = [self.datas[0] floatValue];
      10. for (int i = 0; i < self.datas.count; i++) {
      11. if ([self.datas[i] floatValue] > largeNum) {
      12. largeNum = [self.datas[i] floatValue];
      13. }
      14. }
      15. for (int i = 0; i < self.datas.count - 1; i++) {
      16. x1 = w * i;
      17. x2 = w * (i + 1);
      18. h1 = [self.datas[i] floatValue] / (largeNum * 1.2) * rect.size.height;
      19. h2 = [self.datas[i + 1] floatValue] / (largeNum * 1.2) * rect.size.height;
      20. y1 = rect.size.height - h1;
      21. y2 = rect.size.height - h2;
      22. UIBezierPath *path = [UIBezierPath bezierPath];
      23. [path moveToPoint:CGPointMake(x1, y1)];
      24. [path addLineToPoint:CGPointMake(x2, y2)];
      25. [self.color set];
      26. [path stroke];
      27. }
      28. }
    • 使用

      1. // LineView.h
      2. @interface LineView : UIView
      3. @property (nonatomic, strong) NSArray<NSNumber *> *datas;
      4. @property (nonatomic, strong) UIColor *color;
      5. + (instancetype)lineViewWithFrame:(CGRect)frame
      6. datas:(NSArray<NSNumber *> *)datas
      7. colors:(UIColor *)color;
      8. @end
      9. // LineView.m
      10. + (instancetype)lineViewWithFrame:(CGRect)frame
      11. datas:(NSArray<NSNumber *> *)datas
      12. colors:(UIColor *)color {
      13. LineView *line = [[self alloc] init];
      14. line.frame = frame;
      15. line.datas = datas;
      16. line.color = color;
      17. return line;
      18. }
      19. // ViewController.m
      20. CGRect frame = CGRectMake(50, 40, self.view.bounds.size.width - 100, self.view.bounds.size.width - 100);
      21. NSArray *datas = @[@30, @60, @50, @28];
      22. LineView *lineView = [LineView lineViewWithFrame:frame
      23. datas:datas
      24. colors:[UIColor blueColor]];
      25. lineView.layer.borderWidth = 1;
      26. [self.view addSubview:lineView];
    • 效果

6.2 绘制柱形图

  • 绘制柱形图

    • 方式 1,原始方式

      1. - (void)drawRect:(CGRect)rect {
      2. CGFloat x = 0;
      3. CGFloat y = 0;
      4. CGFloat h = 0;
      5. CGFloat m = self.margin;
      6. CGFloat w = self.margin ? ((rect.size.width - m) / (self.datas.count) - m) :
      7. (rect.size.width / (2 * self.datas.count + 1));
      8. CGFloat largeNum = [self.datas[0] floatValue];
      9. for (int i = 0; i < self.datas.count; i++) {
      10. if ([self.datas[i] floatValue] > largeNum) {
      11. largeNum = [self.datas[i] floatValue];
      12. }
      13. }
      14. for (int i = 0; i < self.datas.count; i++) {
      15. x = self.margin ? ((m + w) * i + m) : (2 * w * i + w);
      16. h = [self.datas[i] floatValue] / (largeNum * 1.2) * rect.size.height;
      17. y = rect.size.height - h;
      18. CGContextRef ctx = UIGraphicsGetCurrentContext();
      19. CGContextAddRect(ctx, CGRectMake(x, y, w, h));
      20. CGContextSetFillColorWithColor(ctx, self.colors[i].CGColor);
      21. CGContextFillPath(ctx);
      22. }
      23. }
    • 方式 2,贝塞尔路径方式

      1. - (void)drawRect:(CGRect)rect {
      2. CGFloat x = 0;
      3. CGFloat y = 0;
      4. CGFloat h = 0;
      5. CGFloat m = self.margin;
      6. CGFloat w = self.margin ? ((rect.size.width - m) / (self.datas.count) - m) :
      7. (rect.size.width / (2 * self.datas.count + 1));
      8. CGFloat largeNum = [self.datas[0] floatValue];
      9. for (int i = 0; i < self.datas.count; i++) {
      10. if ([self.datas[i] floatValue] > largeNum) {
      11. largeNum = [self.datas[i] floatValue];
      12. }
      13. }
      14. for (int i = 0; i < self.datas.count; i++) {
      15. x = self.margin ? ((m + w) * i + m) : (2 * w * i + w);
      16. h = [self.datas[i] floatValue] / (largeNum * 1.2) * rect.size.height;
      17. y = rect.size.height - h;
      18. UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(x, y, w, h)];
      19. [self.colors[i] set];
      20. [path fill];
      21. }
      22. }
    • 使用

      1. // BarView.h
      2. @interface BarView : UIView
      3. @property (nonatomic, strong) NSArray<NSNumber *> *datas;
      4. @property (nonatomic, strong) NSArray<UIColor *> *colors;
      5. @property (nonatomic, assign) CGFloat margin;
      6. + (instancetype)barViewWithFrame:(CGRect)frame
      7. datas:(NSArray<NSNumber *> *)datas
      8. colors:(NSArray<UIColor *> *)colors
      9. margin:(CGFloat)margin;
      10. @end
      11. // BarView.m
      12. + (instancetype)barViewWithFrame:(CGRect)frame
      13. datas:(NSArray<NSNumber *> *)datas
      14. colors:(NSArray<UIColor *> *)colors
      15. margin:(CGFloat)margin {
      16. BarView *bar = [[self alloc] init];
      17. bar.frame = frame;
      18. bar.datas = datas;
      19. bar.colors = colors;
      20. bar.margin = margin;
      21. return bar;
      22. }
      23. // ViewController.m
      24. CGRect frame = CGRectMake(50, 40, self.view.bounds.size.width - 100, self.view.bounds.size.width - 100);
      25. NSArray *datas = @[@30, @60, @50, @28];
      26. NSArray *colors = @[[UIColor redColor], [UIColor yellowColor], [UIColor blueColor], [UIColor greenColor]];
      27. CGFloat margin = 20;
      28. BarView *barView = [BarView barViewWithFrame:frame
      29. datas:datas
      30. colors:colors
      31. margin:margin];
      32. barView.layer.borderWidth = 1;
      33. [self.view addSubview:barView];
    • 效果

6.3 绘制饼图

  • 绘制饼图

    • 方式 1,原始方式

      1. - (void)drawRect:(CGRect)rect {
      2. CGFloat radius = MIN(rect.size.width, rect.size.height) * 0.5;
      3. CGFloat rx = rect.size.width * 0.5;
      4. CGFloat ry = rect.size.height * 0.5;
      5. CGFloat startA = self.startAngle;
      6. CGFloat angle = 0;
      7. CGFloat endA = startA;
      8. CGFloat sum = 0;
      9. for (int i = 0; i < self.datas.count; i++) {
      10. sum += [self.datas[i] floatValue];
      11. }
      12. for (int i = 0; i < self.datas.count; i++) {
      13. startA = endA;
      14. angle = [self.datas[i] floatValue] / sum * M_PI * 2;
      15. endA = startA + angle;
      16. CGContextRef ctx = UIGraphicsGetCurrentContext();
      17. CGContextAddArc(ctx, rx, ry, radius, startA, endA, NO);
      18. CGContextAddLineToPoint(ctx, rx, ry);
      19. CGContextClosePath(ctx);
      20. CGContextSetFillColorWithColor(ctx, self.colors[i].CGColor);
      21. CGContextFillPath(ctx);
      22. }
      23. }
    • 方式 2,贝塞尔路径方式

      1. - (void)drawRect:(CGRect)rect {
      2. CGFloat radius = MIN(rect.size.width, rect.size.height) * 0.5;
      3. CGPoint center = CGPointMake(rect.size.width * 0.5, rect.size.height * 0.5);
      4. CGFloat startA = self.startAngle;
      5. CGFloat angle = 0;
      6. CGFloat endA = startA;
      7. CGFloat sum = 0;
      8. for (int i = 0; i < self.datas.count; i++) {
      9. sum += [self.datas[i] floatValue];
      10. }
      11. for (int i = 0; i < self.datas.count; i++) {
      12. startA = endA;
      13. angle = [self.datas[i] floatValue] / sum * M_PI * 2;
      14. endA = startA + angle;
      15. UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center
      16. radius:radius
      17. startAngle:startA
      18. endAngle:endA
      19. clockwise:YES];
      20. [path addLineToPoint:center];
      21. [path closePath];
      22. [self.colors[i] set];
      23. [path fill];
      24. }
      25. }
    • 使用

      1. // PieView.h
      2. @interface PieView : UIView
      3. @property (nonatomic, strong) NSArray<NSNumber *> *datas;
      4. @property (nonatomic, strong) NSArray<UIColor *> *colors;
      5. @property (nonatomic, assign) CGFloat startAngle;
      6. + (instancetype)pieViewWithFrame:(CGRect)frame
      7. datas:(NSArray<NSNumber *> *)datas
      8. colors:(NSArray<UIColor *> *)colors
      9. startAngle:(CGFloat)startAngle;
      10. @end
      11. // PieView.m
      12. + (instancetype)pieViewWithFrame:(CGRect)frame
      13. datas:(NSArray<NSNumber *> *)datas
      14. colors:(NSArray<UIColor *> *)colors
      15. startAngle:(CGFloat)startAngle {
      16. PieView *pie = [[self alloc] init];
      17. pie.frame = frame;
      18. pie.datas = datas;
      19. pie.colors = colors;
      20. pie.startAngle = startAngle;
      21. return pie;
      22. }
      23. // ViewController.m
      24. CGRect frame = CGRectMake(50, 40, self.view.bounds.size.width - 100, self.view.bounds.size.width - 100);
      25. NSArray *datas = @[@30, @60, @50, @28];
      26. NSArray *colors = @[[UIColor redColor], [UIColor yellowColor], [UIColor blueColor], [UIColor greenColor]];
      27. CGFloat startAngle = -M_PI_2;
      28. PieView *pieView = [PieView pieViewWithFrame:frame
      29. datas:datas
      30. colors:colors
      31. startAngle:startAngle];
      32. pieView.layer.borderWidth = 1;
      33. [self.view addSubview:pieView];
    • 效果

7、使用第三方框架绘制图表

  • 使用第三方框架 Charts 绘制 iOS 图表,Charts 是一款用于绘制图表的框架,可以绘制柱状图、折线图、K线图、饼状图等。GitHub 源码 Charts

  • 具体讲解见 Quartz 2D 第三方框架绘制图表

  • 效果

    • 折线图

    • 柱状图

    • 饼图

8、文本处理

8.1 在控件视图上绘制/添加文本

  • 如果绘制东西到 view 等视图控件上面,必须写在 drawRect 方法里面,不管有没有手动获取到上下文。

  • 1、绘制/添加文本

    • 在指定位置绘制文本,文本不会自动换行

      1. - (void)drawRect:(CGRect)rect {
      2. NSString *string = @"QianChia";
      3. // 不设置文本属性
      4. [string drawAtPoint:CGPointZero withAttributes:nil];
      5. // 设置文本属性
      6. [string drawAtPoint:CGPointZero withAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:50]}];
      7. }
    • 在指定区域绘制文本,文本会自动换行

      1. - (void)drawRect:(CGRect)rect {
      2. NSString *string = @"QianChia";
      3. // 不设置文本属性
      4. [string drawInRect:rect withAttributes:nil];
      5. // 设置文本属性
      6. [string drawInRect:rect withAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:50]}];
      7. }
    • 效果

  • 2、设置文本属性

    1. NSMutableDictionary *textDict = [NSMutableDictionary dictionary];
    2. // 设置文本字体
    3. textDict[NSFontAttributeName] = [UIFont systemFontOfSize:50];
    4. // 设置文本颜色
    5. textDict[NSForegroundColorAttributeName] = [UIColor redColor];
    6. // 设置文本的空心线条宽度
    7. textDict[NSStrokeWidthAttributeName] = @5;
    8. // 设置文本的空心线条颜色,要使此设置有效必须设置空心线条宽度,此设置有效时前景色设置项无效
    9. textDict[NSStrokeColorAttributeName] = [UIColor blueColor];
    10. // 设置文本阴影,用 drawInRect 方式绘制,不添加空心属性时,文字自动换行后此设置无效
    11. NSShadow *shadow = [[NSShadow alloc] init];
    12. shadow.shadowColor = [UIColor blackColor];
    13. shadow.shadowOffset = CGSizeMake(4, 4);
    14. shadow.shadowBlurRadius = 3;
    15. textDict[NSShadowAttributeName] = shadow;
    • 效果

9、图片处理

9.1 在控件视图上绘制/添加图片

  • 如果绘制东西到 view 等视图控件上面,必须写在 drawRect 方法里面,不管有没有手动获取到上下文。

  • 跟 view 相关联的上下文是 layer 图层上下文,需要在在 view 的 drawRect 方法中获取。跟 image 相关的上下文是 Bitmap 位图上下文,需要我们手动创建。

  • 1、绘制/添加图片

    • 在指定位置绘制图片,图片不会进行缩放

      1. - (void)drawRect:(CGRect)rect {
      2. UIImage *image = [UIImage imageNamed:@"demo2"];
      3. [image drawAtPoint:CGPointZero];
      4. }
    • 在指定区域绘制图片,图片会进行缩放

      1. - (void)drawRect:(CGRect)rect {
      2. UIImage *image = [UIImage imageNamed:@"demo2"];
      3. [image drawInRect:rect];
      4. }
    • 在指定区域绘制图片,图片会以平铺的样式填充

      1. - (void)drawRect:(CGRect)rect {
      2. UIImage *image = [UIImage imageNamed:@"demo3"];
      3. [image drawAsPatternInRect:rect];
      4. }
    • 在图片上绘制图片,不是绘制在 view 视图控件上,不需写在 drawRect 方法里面

      1. UIImage *backImage = [UIImage imageNamed:@"demo5"];
      2. UIImage *headImage = [UIImage imageNamed:@"demo6"];
      3. // size :图片画板(上下文)尺寸(新图片的尺寸)
      4. // opaque:是否透明,NO 不透明,YES 透明
      5. // scale :缩放,如果不缩放,设置为 0
      6. // 开启一个位图上下文
      7. UIGraphicsBeginImageContextWithOptions(backImage.size, NO, 0);
      8. // UIGraphicsBeginImageContext(backImage.size);
      9. // 绘制背景图片
      10. CGRect backRect = CGRectMake(0, 0, backImage.size.width, backImage.size.height);
      11. [backImage drawInRect:backRect];
      12. // 绘制头像图片
      13. CGFloat scale = 5;
      14. CGFloat w = backRect.size.width / scale;
      15. CGFloat h = backRect.size.height / scale;
      16. CGFloat x = (backRect.size.width - w) / 2;
      17. CGFloat y = (backRect.size.height - h) / 2;
      18. CGRect headRect = CGRectMake(x, y, w, h);
      19. [headImage drawInRect:headRect];
      20. // 获取绘制好的图片
      21. UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
      22. // 关闭位图上下文
      23. UIGraphicsEndImageContext();
    • 效果

  • 2、图片修剪

    • 裁剪图片

      • 先设置裁剪区域,再在指定的区域绘制图片,再裁剪/遮盖掉裁剪区域之外的部分。

        1. UIImage *image = [UIImage imageNamed:@"demo2"];
        2. // 开启图片上下文
        3. UIGraphicsBeginImageContextWithOptions(image.size, NO, 0);
        4. // UIGraphicsBeginImageContext(image.size);
        5. // 设置裁剪区域,超出裁剪区域的内容全部裁剪/遮盖掉,必须放在绘制图片之前
        6. UIRectClip(CGRectMake(50, 50, 100, 200));
        7. // 绘制图片
        8. [image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)];
        9. // 获取绘制好的图片
        10. UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
        11. // 关闭图片上下文
        12. UIGraphicsEndImageContext();
      • 效果

    • 擦除图片

      • 先在指定的区域绘制图片,再擦除/遮盖掉擦除区域之内的部分。

        1. UIImage *image = [UIImage imageNamed:@"demo2"];
        2. // 开启图片上下文
        3. UIGraphicsBeginImageContextWithOptions(image.size, NO, 0);
        4. // UIGraphicsBeginImageContext(image.size);
        5. // 获取图片上下文
        6. CGContextRef ctx = UIGraphicsGetCurrentContext();
        7. // 绘制图片
        8. [image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)];
        9. // 设置擦除区域,擦除/遮盖掉指定区域的图片,必须放在绘制图片之后
        10. CGContextClearRect(ctx, CGRectMake(50, 50, 100, 200));
        11. // 获取绘制好的图片
        12. UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
        13. // 关闭图片上下文
        14. UIGraphicsEndImageContext();
      • 效果

    • 切割图片

      • 切割掉切割区域之外的部分。

        1. UIImage *image = [UIImage imageNamed:@"demo2"];
        2. // 设置切割区域
        3. CGRect cutRect = CGRectMake(0, 0, image.size.width / 2, image.size.height);
        4. // 切割图片
        5. CGImageRef cgImage = CGImageCreateWithImageInRect(image.CGImage, cutRect);
        6. // 转换为 UIImage 格式图片
        7. UIImage *newImage = [[UIImage alloc] initWithCGImage:cgImage];
        8. CGImageRelease(cgImage);
      • 效果

9.2 截取屏幕

  • 具体实现代码见 GitHub 源码 QExtension

  • 1、截取全屏幕图

    1. @implementation UIImage (Draw)
    2. + (UIImage *)q_imageWithScreenShot {
    3. UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow;
    4. // 开启图片上下文
    5. UIGraphicsBeginImageContextWithOptions(keyWindow.bounds.size, NO, [UIScreen mainScreen].scale);
    6. // UIGraphicsBeginImageContext(keyWindow.bounds.size);
    7. // 获取图片上下文
    8. CGContextRef context = UIGraphicsGetCurrentContext();
    9. // 在 context 上渲染
    10. [keyWindow.layer renderInContext:context];
    11. // 从图片上下文获取当前图片
    12. UIImage *screenShot = UIGraphicsGetImageFromCurrentImageContext();
    13. // 关闭图片上下文
    14. UIGraphicsEndImageContext();
    15. return screenShot;
    16. }
    17. @end
    1. // 截取全屏幕图
    2. UIImage *image = [UIImage q_imageWithScreenShot];
    3. UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);
    • 效果

  • 2、截取指定视图控件屏幕图

    1. @implementation UIImage (Draw)
    2. + (UIImage *)q_imageWithScreenShotFromView:(UIView *)view {
    3. // 开启图片上下文
    4. UIGraphicsBeginImageContextWithOptions(view.bounds.size, NO, [UIScreen mainScreen].scale);
    5. // UIGraphicsBeginImageContext(view.bounds.size);
    6. // 获取图片上下文
    7. CGContextRef context = UIGraphicsGetCurrentContext();
    8. // 在 context 上渲染
    9. [view.layer renderInContext:context];
    10. // 从图片上下文获取当前图片
    11. UIImage *screenShot = UIGraphicsGetImageFromCurrentImageContext();
    12. // 关闭图片上下文
    13. UIGraphicsEndImageContext();
    14. return screenShot;
    15. }
    16. @end
    1. // 截取指定视图控件屏幕图
    2. UIImage *image = [UIImage q_imageWithScreenShotFromView:self.imageView];
    3. UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);
    • 效果

9.3 调整图片尺寸

  • 具体实现代码见 GitHub 源码 QExtension

  • 调整图片尺寸

    1. @implementation UIImage (Draw)
    2. - (UIImage *)q_imageByScalingAndCroppingToSize:(CGSize)size {
    3. // 开启图片上下文
    4. UIGraphicsBeginImageContextWithOptions(size, NO, 0);
    5. // UIGraphicsBeginImageContext(size);
    6. // 在指定的区域内绘制图片
    7. [self drawInRect:CGRectMake(0, 0, size.width, size.height)];
    8. // 从图片上下文获取当前图片
    9. UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    10. // 关闭图片上下文
    11. UIGraphicsEndImageContext();
    12. return image;
    13. }
    14. @end
    1. // 调整图片的尺寸
    2. UIImage *image = [UIImage imageNamed:@"demo2"];
    3. UIImage *newImage = [image q_imageByScalingAndCroppingToSize:CGSizeMake(150, 150)];
    • 效果

9.4 裁剪圆形图片

  • 具体实现代码见 GitHub 源码 QExtension

  • 裁剪圆形图片

    1. @implementation UIImage (Draw)
    2. - (UIImage *)q_imageByCroppingToRound {
    3. // 开启图片上下文
    4. UIGraphicsBeginImageContextWithOptions(self.size, NO, 0);
    5. // UIGraphicsBeginImageContext(self.size);
    6. // 设置裁剪路径
    7. CGFloat w = self.size.width;
    8. CGFloat h = self.size.height;
    9. CGFloat wh = MIN(self.size.width, self.size.height);
    10. CGRect clipRect = CGRectMake((w - wh) / 2, (h - wh) / 2, wh, wh);
    11. UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:clipRect];
    12. // 裁剪
    13. [path addClip];
    14. // 绘制图片
    15. [self drawAtPoint:CGPointZero];
    16. // 从图片上下文获取当前图片
    17. UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    18. // 关闭图片上下文
    19. UIGraphicsEndImageContext();
    20. // 切割图片
    21. CGRect cutRect = CGRectMake(w - wh, h - wh, wh * 2, wh * 2);
    22. CGImageRef imageRef = image.CGImage;
    23. CGImageRef cgImage = CGImageCreateWithImageInRect(imageRef, cutRect);
    24. UIImage *newImage = [[UIImage alloc] initWithCGImage:cgImage];
    25. CGImageRelease(cgImage);
    26. return newImage;
    27. }
    28. @end
    1. // 裁剪圆形图片
    2. UIImage *image = [UIImage imageNamed:@"demo2"];
    3. UIImage *newImage = [image q_imageByCroppingToRound];
    • 效果

9.5 添加图片水印

  • 具体实现代码见 GitHub 源码 QExtension

  • 水印在图片上加的防止他人盗图的半透明 logo、文字、图标。有时候,在手机客户端 app 中也需要用到水印技术。比如,用户拍完照片后,可以在照片上打个水印,标识这个图片是属于哪个用户的。

  • 添加图片水印

    1. @implementation UIImage (Draw)
    2. - (UIImage *)q_imageWithWaterMarkString:(nullable NSString *)string
    3. attributes:(nullable NSDictionary<NSString *, id> *)attrs
    4. image:(nullable UIImage *)image
    5. frame:(CGRect)frame {
    6. // 开启图片上下文
    7. UIGraphicsBeginImageContextWithOptions(self.size, NO, 0);
    8. // UIGraphicsBeginImageContext(self.size);
    9. // 绘制背景图片
    10. CGRect backRect = CGRectMake(0, 0, self.size.width, self.size.height);
    11. [self drawInRect:backRect];
    12. CGRect strRect = frame;
    13. // 添加图片水印
    14. if (image) {
    15. if ((frame.origin.x == -1) && (frame.origin.y == -1)) {
    16. CGFloat w = frame.size.width;
    17. CGFloat h = frame.size.height;
    18. CGFloat x = (backRect.size.width - w) / 2;
    19. CGFloat y = (backRect.size.height - h) / 2;
    20. [image drawInRect:CGRectMake(x, y, w, h)];
    21. } else {
    22. [image drawInRect:frame];
    23. strRect = CGRectMake(frame.origin.x + frame.size.width + 5, frame.origin.y, 1, 1);
    24. }
    25. }
    26. // 添加文字水印
    27. if (string) {
    28. if ((frame.origin.x == -1) && (frame.origin.y == -1)) {
    29. } else {
    30. [string drawAtPoint:strRect.origin withAttributes:attrs];
    31. }
    32. }
    33. // 获取绘制好的图片
    34. UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    35. // 关闭位图上下文
    36. UIGraphicsEndImageContext();
    37. return newImage;
    38. }
    39. @end
    1. UIImage *image = [UIImage imageNamed:@"demo2"];
    2. // 设置水印文本属性
    3. NSMutableDictionary *textAttrs = [NSMutableDictionary dictionary];
    4. textAttrs[NSFontAttributeName] = [UIFont boldSystemFontOfSize:50];
    5. textAttrs[NSForegroundColorAttributeName] = [[UIColor redColor] colorWithAlphaComponent:0.2];
    6. textAttrs[NSStrokeWidthAttributeName] = @5;
    7. // 添加图片水印
    8. self.imageView.image = [image q_imageWithWaterMarkString:@"QianChia"
    9. attributes:textAttrs
    10. image:nil
    11. frame:CGRectMake(30, 300, 50, 50)];
    1. UIImage *image = [UIImage imageNamed:@"demo5"];
    2. // 添加图片水印
    3. self.imageView.image = [image q_imageWithWaterMarkString:nil
    4. attributes:nil
    5. image:[UIImage imageNamed:@"demo8"]
    6. frame:CGRectMake(-1, -1, 88, 88)];
    • 效果

10、Quartz 2D 的使用

10.1 绘制下载进度按钮

  • 具体实现代码见 GitHub 源码 QExtension

  • 具体讲解见 Quartz 2D 下载进度按钮绘制

    1. // 创建进度按钮
    2. QProgressButton *progressButton = [QProgressButton q_progressButtonWithFrame:CGRectMake(100, 100, 100, 50)
    3. title:@"开始下载"
    4. lineWidth:10
    5. lineColor:[UIColor blueColor]
    6. textColor:[UIColor redColor]
    7. backgroundColor:[UIColor yellowColor]
    8. isRound:YES];
    9. // 设置按钮点击事件
    10. [progressButton addTarget:self action:@selector(progressUpdate:) forControlEvents:UIControlEventTouchUpInside];
    11. // 将按钮添加到当前控件显示
    12. [self.view addSubview:progressButton];
    13. // 设置按钮的进度值
    14. self.progressButton.progress = progress;
    15. // 设置按钮的进度终止标题,一旦设置了此标题进度条就会停止
    16. self.progressButton.stopTitle = @"下载完成";
  • 效果

10.2 绘制手势截屏

  • 具体实现代码见 GitHub 源码 QExtension

  • 具体讲解见 Quartz 2D 手势截屏绘制

    1. // 创建手势截屏视图
    2. QTouchClipView *touchClipView = [QTouchClipView q_touchClipViewWithView:self.imageView
    3. clipResult:^(UIImage * _Nullable image) {
    4. // 获取处理截屏结果
    5. if (image) {
    6. UIImageWriteToSavedPhotosAlbum(image, self, @selector(image:didFinishSavingWithError:contextInfo:), nil);
    7. }
    8. }];
    9. // 添加手势截屏视图
    10. [self.view addSubview:touchClipView];
  • 效果

10.3 绘制手势锁

  • 具体实现代码见 GitHub 源码 QExtension

  • 具体讲解见 Quartz 2D 手势锁绘制

    1. // 设置 frame
    2. CGFloat margin = 50;
    3. CGFloat width = self.view.bounds.size.width - margin * 2;
    4. CGRect frame = CGRectMake(margin, 200, width, width);
    5. // 创建手势锁视图界面,获取滑动结果
    6. QTouchLockView *touchLockView = [QTouchLockView q_touchLockViewWithFrame:frame
    7. pathResult:^(BOOL isSucceed, NSString * _Nonnull result) {
    8. // 处理手势触摸结果
    9. [self dealTouchResult:result isSucceed:isSucceed];
    10. }];
    11. [self.view addSubview:touchLockView];
    • 效果

10.4 绘制画板

10.4.1 绘制简单画板

  • 绘制简单画板

    1. // 创建画板
    2. CGRect frame = CGRectMake(20, 50, self.view.bounds.size.width - 40, 200);
    3. PaintBoardView *paintBoard = [[PaintBoardView alloc] initWithFrame:frame];
    4. [self.view addSubview:paintBoard];
  • 效果

10.4.2 绘制画板封装

  • 具体实现代码见 GitHub 源码 QExtension

  • 1、创建简单画板

    1. // 创建简单画板
    2. CGRect frame = CGRectMake(20, 50, self.view.bounds.size.width - 40, 200);
    3. QPaintBoardView *paintBoardView = [QPaintBoardView q_paintBoardViewWithFrame:frame];
    4. // 可选属性值设置
    5. paintBoardView.paintLineWidth = 5; // default is 1
    6. paintBoardView.paintLineColor = [UIColor redColor]; // default is blackColor
    7. paintBoardView.paintBoardColor = [UIColor cyanColor]; // default is whiteColor
    8. [self.view addSubview:paintBoardView];
    9. self.paintBoardView = paintBoardView;
    10. // 撤销绘画结果
    11. [self.paintBoardView q_back];
    12. // 清除绘画结果
    13. [self.paintBoardView q_clear];
    14. // 获取绘画结果
    15. UIImage *image = [self.paintBoardView q_getPaintImage];
    • 效果

  • 2、创建画板

    1. // 创建画板
    2. QPaintBoardView *paintBoard = [QPaintBoardView q_paintBoardViewWithFrame:self.view.bounds
    3. lineWidth:0
    4. lineColor:nil
    5. boardColor:nil
    6. paintResult:^(UIImage * _Nullable image) {
    7. if (image) {
    8. NSData *data = UIImagePNGRepresentation(image);
    9. [data writeToFile:@"/Users/JHQ0228/Desktop/Images/pic.png" atomically:YES];
    10. }
    11. }];
    12. [self.view addSubview:paintBoard];
    • 效果

10.5 刮奖模拟

  • 刮奖

    1. - (IBAction)scratchBtnClick:(id)button {
    2. [button removeFromSuperview];
    3. [self.forImageView removeFromSuperview];
    4. }
    5. - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    6. CGPoint startPoint = [touches.anyObject locationInView:self.cerImageView];
    7. CGRect startRect = CGRectMake(startPoint.x - 10, startPoint.y - 10, 20, 20);
    8. [self clearRect:startRect imageView:self.cerImageView];
    9. }
    10. - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    11. CGPoint touchPoint = [touches.anyObject locationInView:self.cerImageView];
    12. CGRect touchRect = CGRectMake(touchPoint.x - 10, touchPoint.y - 10, 20, 20);
    13. [self clearRect:touchRect imageView:self.cerImageView];
    14. }
    15. - (void)clearRect:(CGRect)rect imageView:(UIImageView *)imageView {
    16. UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, NO, 0);
    17. CGContextRef ctx = UIGraphicsGetCurrentContext();
    18. [imageView.layer renderInContext:ctx];
    19. // 设置擦除区域
    20. CGContextClearRect(ctx, rect);
    21. UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    22. imageView.image = newImage;
    23. UIGraphicsEndImageContext();
    24. }
    • 效果

iOS - Quartz 2D 二维绘图的更多相关文章

  1. iOS - Quartz 2D 贝塞尔曲线

    1.贝塞尔曲线 贝塞尔曲线(Bézier curve),又称贝兹曲线或贝济埃曲线,是应用于二维图形应用程序的数学曲线.一般的矢量图形软件通过它来精确画出曲线,贝兹曲线由线段与节点组成,节点是可拖动的支 ...

  2. Agg vs. Cairo 二维绘图引擎之比较和选择 .

    Agg vs. Cairo 二维绘图引擎之比较和选择 cheungmine 当今时代对于作为二维图形软件开发者, 是幸运的.因为除了Windows GDI/GDI+之外,我们还有很多其他的选择.而且这 ...

  3. TurboCAD Pro for Mac(二维绘图和三维建模工具)破解版安装

    1.软件简介    TurboCAD Pro 是 macOS 系统上一款二维绘图和三维建模工具,具备强大的绘图和设计特性,加上强大的创建复杂的三维模型的工具,三维 OpenGL 的渲染,和超过 11, ...

  4. 微信连WiFi关注公众号流程更新 解决ios微信扫描二维码不关注就能上网的问题

    前几天鼓捣了一下微信连WiFi功能,设置还蛮简单的,但ytkah发现如果是ios版微信扫描微信连WiFi生成的二维码不用关注公众号就可以直接上网了,而安卓版需要关注公众号才能上网,这样就少了很多ios ...

  5. Matlab 二维绘图函数(plot类)

    plot 功能 绘制二维图形的最基本函数. 语法 //x为向量时,以x的元素值为纵坐标,x的序号为横坐标绘制曲线. //x为矩阵时,以其序号为横坐标,按列绘制每列元素值相对于其序号的曲线. polt( ...

  6. matlab学习笔记8 基本绘图命令-初级二维绘图/交互式绘图

    一起来学matlab-matlab学习笔记8 基本绘图命令_5 初级二维绘图/交互式绘图 觉得有用的话,欢迎一起讨论相互学习~Follow Me 参考书籍 <matlab 程序设计与综合应用&g ...

  7. iOS 读取相册二维码,兼容ios7(使用CIDetector 和 ZXingObjC)

    ios从相册读取二维码,在ios8以上,苹果提供了自带的识别图片二维码的功能,这种方式效率最好,也是最推荐的,但是如果你的系统需要向下兼容ios7,就必须用其他方式. 这里我选择的是 ZXingObj ...

  8. iOS开发——生成二维码——工具类

    啥也不说,直接上源码,拷过去就能用.生成二维码的工具类使用方法在ProduceQRCode.h里有示例说明 分别将下面的ProduceQRCode.h和ProduceQRCode.m对应的代码考到自己 ...

  9. ios 中生成二维码和相册中识别二维码

    iOS 使用CIDetector扫描相册二维码.原生扫描 原生扫描 iOS7之后,AVFoundation让我们终于可以使用原生扫描进行扫码了(二维码与条码皆可)AVFoundation可以让我们从设 ...

随机推荐

  1. 一个巨low的“2048”

    代码就是这样,做的不是4*4而是一个2*2 #include<stdio.h>#include<stdlib.h>#include<time.h>int main( ...

  2. 3.C++内联函数,默认参数,占位参数

    本章主要内容: 1)内联函数(替代宏代码段) 2)默认参数 3)占位参数 1.C++的内联函数分析 1.1讲解内联函数之前,首先回忆下之前讲的define宏定义: 之前讲过宏定义会经过预处理器进行文本 ...

  3. 福建百度seo和推广,关键词排名优化,网络营销推广培训

    福建百度seo和推广,关键词排名优化,网络营销推广培训 福建百度seo和推广,关键词排名优化,网络营销推广培训,那么如何才能够让自己的文章信息被百度收录呢?只要说自己的文章能够被百度收录,那么你的信息 ...

  4. shell实现centos7双网卡修改网卡名eth0,eth1,并设置网络

    #!/bin/bash interface1=`ls /sys/class/net|grep en|awk 'NR==1{print}'` interface2=`ls /sys/class/net| ...

  5. 哪些CSS是可以被继承的--简单整理

    那些CSS是可以被继承的--简单整理1.文本相关属性是继承的:font-size,font-family,line-height,text-index等2.列表相关属性是继承的:list-style- ...

  6. python学习:简单的wc命令实现

    #!/usr/bin/python   import sys import os   try:     fn = sys.argv[1] except IndexError:     print &q ...

  7. Java经典编程题50道之四十四

    求0~7所能组成的奇数个数.分析:组成1位数是4个,组成2位数是7*4个,组成3位数是7*8*4个,组成4位数是7*8*8*4个…… public class Example44 {    publi ...

  8. ASP.NET Core的身份认证框架IdentityServer4--(4)添加第三方快捷登录

    添加对外部认证的支持 接下来我们将添加对外部认证的支持.这非常简单,因为你真正需要的是一个兼容ASP.NET Core的认证处理程序. ASP.NET Core本身也支持Google,Facebook ...

  9. Shiro笔记--shiroFilter权限过滤

    1.shiro中shiroFilter中的一些配置页面的过滤权限 <!--名字必须和web.xml里面的filter-name一样--> <bean id="shiroFi ...

  10. Activiti中的各个service的作用

    各个Service的作用: RepositoryService 管理流程定义 RuntimeService 执行管理,包括启动.推进.删除流程实例等操作 TaskService 任务管理 Histor ...