玩转iOS中的绘图(Quartz 2D基础篇)

前言: 本篇简单介绍使用Quartzs 2D来绘图.如果你在网上搜索关于iOS开发中对于图片的处理(缩放...),或者运用截取屏幕或者某一部分内容来实现一些动画交互(比如实现tableViewCell, collectionView的移动效果...), 以及自定义一些控件(比如圆形进度条...)那么大多数的处理都会涉及到绘图的处理, 也许很多功能在你需要实现的时候, 搜索一下可以找到一些解决问题的代码, 但是或许在使用很久之后还是不明白其中的知识点. 本篇大多内容源于对官方文档"Quartz 2D Programming Guide"的理解.

首先对于Quartz 2D就不多做介绍了,(处理路径的绘图, 透明度绘图, 遮盖,阴影, 颜色管理, 防锯齿渲染, 生成PDF...) 感兴趣的朋友可以查查相关的资料

1. 了解Quartz 2D的绘图机制

  • 和现实中的绘图一样, 首先需要一张"画布", 在iOS中被称为 "图形上下文", 这个图形上下文包含了设备的信息等很多系统准备好的内容
  • 在上下文中运用一些方法来设置我们要绘制的内容和属性,来实现绘图
  • Quartz 2D类似打印机, 对不同的内容的绘图顺序不同, 得到的结果就不相同
    这里有一张官方的图的解释,很直接


    painters_model.gif

2.获取上下文

  • 重写UIView的drawRect()方法, 当这个view的内容需要更新的时候会调用这个方法, 所以系统在这个方法里面会默认提供一个上下文, 可以通过UIGraphicsGetCurrentContext() 获取到
  • 在其他地方可以通过UIGraphicsBeginImageContextWithOptions()开启一个上下文, 然后通过 UIGraphicsGetCurrentContext() 可以获取到
  • 关于drawRect()方法(处理不当时有人称为"内存恶魔")
    // 调用setNeedsDisplay()这个函数会触发drawRect方法, 当这个函数被调用的时候, 系统会通知这个UIView整个界面需要重绘. 但是这个函数会直接返回, 但界面的变化不会马上返回, 需要等到下个生命周期进行重绘
    // 如果只有一部分需要重绘 可以调用这个方法 setNeedsDisplayInRect(rect: CGRect)
    // 这个方法里面应该只做与界面的绘制有关的工作, 不应该进行任何的数据处理或者程序的逻辑相关的任务, 以保证尽快执行完毕

3. 坐标系

  • 和数学中的直角坐标系相同, 原点在左下角, UIKit中的坐标系的原点在左上角, 所以使用Quartz 2D绘图的时候要注意进行坐标系的转换

4. 内存管理

  • 因为使用的C语言的东西, 编译器并没有给我们自动管理内存, 在oc中需要我们自己来管理, 即当调用含有create或者copy的函数获得上下文的时候, 需要在使用完后手动release, 但在swift中我们不用管理了(而且C语言的函数在swift中看上去也比较习惯)

5. 绘制我们需要的内容

  • 5.1 绘制路径

    • 首先需要我们画出路径 -> 指定起始点到另一个点, 然后两点之间可以使用直线或者曲线来连接, 就形成了一条路径, 例如使用下面的函数来实现
    ```
    

    CGContextMoveToPoint() -> 指定起始点或者移动到新的点
    CGContextAddLineToPoint() -> 从当前的点到指定的点之间画一条线
    CGContextAddArc() -> 圆弧
    CGContextAddArcToPoint() -> 会在当前点和指定点与坐标系平行的直线做切线画圆弧
    CGContextAddCurveToPoint() -> 画曲线(bezier)
    CGContextClosePath() -> 将起始点和结束点连接起来形成封闭的路径
    CGContextAddEllipseInRect() -> 椭圆
    ```

    • 然后设置需要绘制的路径的属性 -> 设置颜色,透明度,宽度, 绘制模式...例如使用如下的函数来实现
    ```
    

CGContextSetLineWidth() -> 线宽度
CGContextSetLineJoin() -> 线的连接点的样式
CGContextSetLineCap() -> 端点的样式
CGContextSetLineDash() -> 虚线
CGContextSetStrokeColorWithColor() -> stroke模式时的颜色
CGContextSetFillColorWithColor() -> fill模式时的颜色
```
* 绘制内容, 注意会涉及到两种方式: fill 和 stroke, 这里解释一下两者的区别

  • 当使用stroke方式 -> "描边"即只绘制路径
  • 当使用fill方式"填充"即绘制路径包括的所有区域(所有路径都会被当作closePath处理)

  • Filling有两种模式
    nonzero winding number(默认) -> 如果两个路径有重叠的时候, 绘制方向相同的话, 那么重叠部分的绘制可能不是我们希望的
    even-odd -> 不受绘制方向的影响
    CGContextEOFillPath
    CGContextFillPath
    CGContextFillRect
    CGContextFillRects
    CGContextFillEllipseInRect

      CGContextStrokePath(context)
      CGContextFillPath(context)
    
    
  • 示例
    stroke模式绘制线

```
override func drawRect(rect: CGRect) {
    // Drawing code
    // 获取当前上下文
    let context = UIGraphicsGetCurrentContext()
    let strokeColor = UIColor.blueColor()
    // 设置stroke模式的颜色使用CGColor(这里涉及到颜色和颜色空间的概念)
    CGContextSetStrokeColorWithColor(context, strokeColor.CGColor)
    // 设置线的宽度
    CGContextSetLineWidth(context, 5.0)
    // 设置连接处样式...还可以设置很多其他的属性
    CGContextSetLineJoin(context, CGLineJoin.Round)
    // 起始点
    CGContextMoveToPoint(context, 10.0, 10.0)
    // 在点(10.0, 10.0)和(10.0, 80.0)之间画一条线 所以点(10.0, 80.0)被设置为下一个起始点
    CGContextAddLineToPoint(context, 10.0, 80.0)
    //在点(10.0, 80.0)和(80.0, 80.0)之间画一条线 所以点(80.0, 80.0)被设置为下一个起始点
    CGContextAddLineToPoint(context, 80.0, 80.0)
    // 设置为封闭路径, 会将首尾点连接起来
    // CGContextClosePath(context)
    // 绘制当前的路径
    CGContextStrokePath(context)
    
}

```
不封闭路径png
封闭路径.png

fill模式绘制相同的路径

     let fillColor = UIColor.blueColor()
     // 设置fill模式的颜色使用CGColor(这里涉及到颜色和颜色空间的概念)
     CGContextSetFillColorWithColor(context, fillColor.CGColor)
     // 起始点
     CGContextMoveToPoint(context, 10.0, 10.0)
     // 在点(10.0, 10.0)和(10.0, 80.0)之间画一条线 所以点(10.0, 80.0)被设置为下一个起始点
     CGContextAddLineToPoint(context, 10.0, 80.0)
     //在点(10.0, 80.0)和(80.0, 80.0)之间画一条线 所以点(80.0, 80.0)被设置为下一个起始点
     CGContextAddLineToPoint(context, 80.0, 80.0)
     //        CGContextClosePath(context)
     // 绘制当前的路径
     CGContextFillPath(context)
   ```

![fill方式.png](http://upload-images.jianshu.io/upload_images/1271831-3729cb7a3c50b757.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

stroke模式绘制圆

    // 设置stroke模式的颜色使用CGColor(这里涉及到颜色和颜色空间的概念)
    CGContextSetStrokeColorWithColor(context, strokeColor.CGColor)
    // 设置线的宽度
    CGContextSetLineWidth(context, 5.0)
    // 设置连接处样式
    CGContextSetLineJoin(context, CGLineJoin.Round)
    // 起始点
    let circleCenter = CGPoint(x: rect.width * 0.5, y: rect.height * 0.5)
    CGContextMoveToPoint(context, circleCenter.x*2, circleCenter.y)
    // 画圆
    // x -> 圆心的x坐标
    // y -> 圆心的y坐标
    // radius -> 圆的半径
    // startAngle -> 起始角度 (弧度制) 所以上面调整了起始点
    // endAngle -> 结束角度(弧度制)
    CGContextAddArc(context, circleCenter.x, circleCenter.y, rect.width * 0.5, 0.0, CGFloat(M_PI) * 2, 0)
    CGContextStrokePath(context)

![stroke圆png](http://upload-images.jianshu.io/upload_images/1271831-80e1a3a80711373c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

fill方式绘制圆

    // 设置fill模式的颜色使用CGColor(这里涉及到颜色和颜色空间的概念)
    CGContextSetFillColorWithColor(context, fillColor.CGColor)
    // 起始点
    let circleCenter = CGPoint(x: rect.width * 0.5, y: rect.height * 0.5)
    CGContextMoveToPoint(context, circleCenter.x*2, circleCenter.y)
    // 画圆
    // x -> 圆心的x坐标
    // y -> 圆心的y坐标
    // radius -> 圆的半径
    // startAngle -> 起始角度 (弧度制) 所以上面调整了起始点
    // endAngle -> 结束角度(弧度制)
    CGContextAddArc(context, circleCenter.x, circleCenter.y, rect.width * 0.5, 0.0, CGFloat(M_PI) * 2, 0)
    // 绘制当前的路径
    CGContextFillPath(context)

![fill方式.png](http://upload-images.jianshu.io/upload_images/1271831-da58c13499f817c6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

-----
#####仅仅介绍了上面的几种看上去很简单的绘制方法, 也许你会觉得没有什么实际用处, 但是实际上是很有用处的, 因为到现在为止, 你可以使用Quartzs 2D来实现自定义的各种圆形进度条了,(可能使用到stroke和fill的混合模式), 只需要在提供的progress属性设置时调用setNeedsDisplay()就可以触发drawRect()方法进行重绘, 而在这里面你就可以利用progress来改变绘制的内容了, 从而实现进度条的效果
---
> 限于篇幅和时间, 这篇中只是梳理了一些基本的绘图概念和绘制简单的图形(实际上看看API就可以类似的使用相关的方法绘制出其他的图形 -- 椭圆...), 而实际上还有比较多的东西需要研究, 以后有必要在补上. [Demo](https://github.com/jasnig/DrawingStudy)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容

  • --绘图与滤镜全面解析 概述 在iOS中可以很容易的开发出绚丽的界面效果,一方面得益于成功系统的设计,另一方面得益...
    韩七夏阅读 2,631评论 2 10
  • Quartz 2D是一个二维图形绘制引擎,支持iOS环境和Mac OS X环境。我们可以使用Quartz 2D A...
    Eiwodetianna阅读 1,949评论 1 251
  • 坐车回家路上路过麻城站,刚好遇到今年冬天最大的一场雪,一路上大学纷飞,白雪皑皑。对面有辆停下的列车,车上的乘客好像...
    林文意阅读 456评论 0 1
  • 最近总是能在各种网站上看见许多浓浓的“鸡汤”,上面针对思考方面我觉得千篇一律,无非都说凡事不要想太多,想太多注定就...
    瓷猫阅读 307评论 0 0
  • 我到目前为止觉得微积分不难,但为什么我的微积分考试的成绩总是不高呢?考试的时候总是出错呢?
    邓茜Daisy阅读 76评论 0 0