iOS绘图(一)之UIBezierPath

UIBezierPath介绍

iOS系统本身提供了两套绘图的框架,即UIBezierPath 和 Core Graphics。而前者所属UIKit,是CGPathRef数据类型的封装。如果是基于矢量形状的路径,都用直线和曲线去创建。我们使用直线段去创建矩形和多边形,使用曲线去创建圆弧(arc)、圆或者其他复杂的曲线形状。

属性介绍

  • CGPath:将UIBezierPath类转换成CGPath,类似于UIColor的CGColor
  • empty:只读类型,路径上是否有有效的元素
  • bounds:和view的bounds是不一样的,它获取path的X坐标、Y坐标、宽度,但是高度为0
  • currentPoint:当前path的位置,可以理解为path的终点
  • lineWidth:path宽度
  • lineCapStyle:path端点样式,有3种样式
    kCGLineCapButt:无端点
    kCGLineCapRound:圆形端点
    kCGLineCapSquare:方形端点(样式上和kCGLineCapButt是一样的,但是比kCGLineCapButt长一点)
  • lineJoinStyle:拐角样式
    kCGLineJoinMiter:尖角
    kCGLineJoinRound:圆角
    kCGLineJoinBevel:缺角
  • miterLimit:最大斜接长度(只有在使用kCGLineJoinMiter是才有效), 边角的角度越小,斜接长度就会越大
  • flatness:弯曲路径的渲染精度,默认为0.6,越小精度越高,相应的更加消耗性能。
  • usesEvenOddFillRule:单双数圈规则是否用于绘制路径,默认是NO。
  • UIRectCorner:角
    UIRectCornerTopLeft:左上角
    UIRectCornerTopRight:右上角
    UIRectCornerBottomLeft:左下角
    UIRectCornerBottomRight:右下角
    UIRectCornerAllCorners:所有四个角

方法介绍

//创建并且返回一个新的 `UIBezierPath` 对象
+ (instancetype) bezierPath;
/**
  * 该方法将会创建一个闭合路径, 起始点是 rect 参数的的 origin, 并且按照顺时针方向添加直线, 最终形成矩形
  * @param rect:   矩形路径的 Frame
  */
+ (instancetype)bezierPathWithRect:(CGRect)rect;
/**
  * 该方法将会创建一个闭合路径,  该方法会通过顺时针的绘制贝塞尔曲线, 绘制出一个近似椭圆的形状. 如果 rect 参数指定了一个矩形, 那么该 UIBezierPath 对象将会描述一个圆形.
  * @param rect:   矩形路径的 Frame
  */
+ (instancetype)bezierPathWithOvalInRect:(CGRect)rect;
/**
  * 该方法将会创建一个闭合路径,  该方法会顺时针方向连续绘制直线和曲线.  当 rect 为正方形时且 cornerRadius 等于边长一半时, 则该方法会描述一个圆形路径.
  * @param rect:   矩形路径的 Frame
  * @param cornerRadius:   矩形的圆角半径
  */
+ (instancetype) bezierPathWithRoundedRect:(CGRect)rect 
                              cornerRadius:(CGFloat)cornerRadius;
/**
  * 该方法将会创建一个闭合路径,  该方法会顺时针方向连续绘制直线和曲线.  
  * @param rect:   矩形路径的 Frame
  * @param corners:   UIRectCorner 枚举类型, 指定矩形的哪个角变为圆角
  * @param cornerRadii:   矩形的圆角半径
  */
+ (instancetype) bezierPathWithRoundedRect:(CGRect)rect 
                         byRoundingCorners:(UIRectCorner)corners
                               cornerRadii:(CGSize)cornerRadii;
/**
  * 该方法会创建出一个开放路径, 创建出来的圆弧是圆的一部分. 在默认的坐标系统中, 开始角度 和 结束角度 都是基于单位圆的(看下面这张图). 调用这个方法之后, currentPoint 将会设置为圆弧的结束点.
  * 举例来说: 指定其实角度为0, 指定结束角度为π, 设置 clockwise 属性为 YES, 将会绘制出圆的下半部分.
  * 然而当我们不修改起始角度 和 结束角度, 我们仅仅将 clockwise 角度设置为 NO, 则会绘制出来一个圆的上半部分.
  * @param center:   圆心
  * @param radius: 半径
  * @param startAngle:   起始角度
  * @param endAngle:   结束角度
  * @param clockwise:   是否顺时针绘制
  */
+ (instancetype) bezierPathWithArcCenter:(CGPoint)center 
                                  radius:(CGFloat)radius 
                              startAngle:(CGFloat)startAngle 
                                endAngle:(CGFloat)endAngle 
                               clockwise:(BOOL)clockwise;
//通过一个 CGPath, 创建并且返回一个新的 `UIBezierPath` 对象
+ (instancetype) bezierPathWithCGPath:(CGPathRef)CGPath;
/**
  * 通过该方法反转一条路径, 并不会修改该路径的样子. 它仅仅是修改了绘制的方向
  * @return: 返回一个新的 UIBezierPath 对象, 形状和原来路径的形状一样,
  *          但是绘制的方向相反.
  */
- (UIBezierPath *) bezierPathByReversingPath;
/**
  * 如果当前有正在绘制的子路径, 该方法则会隐式的结束当前路径, 
  * 并将 currentPoint 设置为指定点. 当上一条子路径被终止, 该方法
  * 实际上并不会去闭合上一条子路径. 所以上一条自路径的起始点 和
  * 结束点并没有被链接.
  * 对于大多数构造路径相关的方法而言, 在你绘制直线或曲线之前, 需要先调用这个方法.
  * @param point:   当前坐标系统中的某一点
  */
- (void)moveToPoint:(CGPoint)point;
/**
  * 该方法将会从 currentPoint 到 指定点 链接一条直线. 
  * Note: 在追加完这条直线后, 该方法将会更新 currentPoint 为 指定点
  *       调用该方法之前, 你必须先设置 currentPoint. 如果当前绘制路径
  *       为空, 并且未设置 currentPoint, 那么调用该方法将不会产生任何
  *       效果.
  * @param point:   绘制直线的终点坐标, 当前坐标系统中的某一点
  */
- (void)addLineToPoint:(CGPoint)point;
/**
  * 该方法将会从 currentPoint 添加一条指定的圆弧.
  * 该方法的介绍和构造方法中的一样. 请前往上文查看
  * @param center: 圆心
  * @param radius: 半径
  * @param startAngle: 起始角度
  * @param endAngle: 结束角度
  * @param clockwise: 是否顺时针绘制
  */
- (void)addArcWithCenter:(CGPoint)center 
                  radius:(CGFloat)radius 
              startAngle:(CGFloat)startAngle 
                endAngle:(CGFloat)endAngle 
               clockwise:(BOOL)clockwise NS_AVAILABLE_IOS(4_0);
/**
  * 该方法将会从 currentPoint 到 指定的 endPoint 追加一条三次贝塞尔曲线.
  * 三次贝塞尔曲线的弯曲由两个控制点来控制. 如下图所示
  * Note: 调用该方法前, 你必须先设置 currentPoint, 如果路径为空, 
  *       并且尚未设置 currentPoint, 调用该方法则不会产生任何效果. 
  *       当添加完贝塞尔曲线后, 该方法将会自动更新 currentPoint 为
  *       指定的结束点
  * @param endPoint: 终点
  * @param controlPoint1: 控制点1
  * @param controlPoint2: 控制点2
  */
- (void)addCurveToPoint:(CGPoint)endPoint 
          controlPoint1:(CGPoint)controlPoint1 
          controlPoint2:(CGPoint)controlPoint2;
/**
  * 该方法将会从 currentPoint 到 指定的 endPoint 追加一条二次贝塞尔曲线.
  * currentPoint、endPoint、controlPoint 三者的关系最终定义了二次贝塞尔曲线的形状.
  * 二次贝塞尔曲线的弯曲由一个控制点来控制. 如下图所示
  * Note: 调用该方法前, 你必须先设置 currentPoint, 如果路径为空, 
  *       并且尚未设置 currentPoint, 调用该方法则不会产生任何效果. 
  *       当添加完贝塞尔曲线后, 该方法将会自动更新 currentPoint 为
  *       指定的结束点
  * @param endPoint: 终点
  * @param controlPoint: 控制点
  */
- (void)addQuadCurveToPoint:(CGPoint)endPoint 
               controlPoint:(CGPoint)controlPoint;
/**
  * 该方法将会从 currentPoint 到子路经的起点 绘制一条直线, 
  * 以此来关闭当前的自路径. 紧接着该方法将会更新 currentPoint
  * 为 刚添加的这条直线的终点, 也就是当前子路经的起点.
  */
- (void)closePath;
/**
  * 该方法将会在当前 UIBezierPath 对象的路径中追加
  * 指定的 UIBezierPath 对象中的内容. 
  */
- (void)appendPath:(UIBezierPath *)bezierPath;
/**
  * @param pattern: 该属性是一个 C 语言的数组, 其中每一个元素都是 CGFloat
  *                 数组中的元素代表着线段每一部分的长度, 第一个元素代表线段的第一条线,
  *                 第二个元素代表线段中的第一个间隙. 这个数组中的值是轮流的. 来解释一下
  *                 什么叫轮流的. 
  *                 举个例子: 声明一个数组 CGFloat dash[] = @{3.0, 1.0}; 
  *                 这意味着绘制的虚线的第一部分长度为3.0, 第一个间隙长度为1.0, 虚线的
  *                 第二部分长度为3.0, 第二个间隙长度为1.0. 以此类推.

  * @param count: 这个参数是 pattern 数组的个数
  * @param phase: 这个参数代表着, 虚线从哪里开始绘制.
  *                 举个例子: 这是 phase 为 6. pattern[] = @{5, 2, 3, 2}; 那么虚线将会
  *                 第一个间隙的中间部分开始绘制, 如果不是很明白就请继续往下看,
  *                 下文实战部分会对虚线进行讲解.
  */
- (void)setLineDash:(const CGFloat *)pattern
              count:(NSInteger)count
              phase:(CGFloat)phase;
/**
  * 该方法可以重新获取之前设置过的虚线样式.
  *  Note:  pattern 这个参数的容量必须大于该方法返回数组的容量.
  *         如果无法确定数组的容量, 那么可以调用两次该方法, 第一次
  *         调用该方法的时候, 传入 count 参数, 然后在用 count 参数
  *         来申请 pattern 数组的内存空间. 然后再第二次正常的调用该方法
  */
- (void)getLineDash:(CGFloat *)pattern 
              count:(NSInteger *)count
              phase:(CGFloat *)phase;
/**
  * 该方法当前的填充颜色 和 绘图属性对路径的封闭区域进行填充.
  * 如果当前路径是一条开放路径, 该方法将会隐式的将路径进行关闭后进行填充
  * 该方法在进行填充操作之前, 会自动保存当前绘图的状态, 所以我们不需要
  * 自己手动的去保存绘图状态了. 
  */
- (void)fill;
/**
  * 该方法当前的填充颜色 和 绘图属性 (外加指定的混合模式 和 透明度) 
  * 对路径的封闭区域进行填充. 如果当前路径是一条开放路径, 该方法将
  * 会隐式的将路径进行关闭后进行填充
  * 该方法在进行填充操作之前, 会自动保存当前绘图的状态, 所以我们不需要
  * 自己手动的去保存绘图状态了. 
  *
  * @param blendMode: 混合模式决定了如何和已经存在的被渲染过的内容进行合成
  * @param alpha: 填充路径时的透明度
  */
- (void)fillWithBlendMode:(CGBlendMode)blendMode 
                    alpha:(CGFloat)alpha;
//绘制路径
- (void)stroke;
/**
  * @param blendMode: 混合模式决定了如何和已经存在的被渲染过的内容进行合成
  * @param alpha: 填充路径时的透明度
  */
- (void)strokeWithBlendMode:(CGBlendMode)blendMode
                      alpha:(CGFloat)alpha;
/**
  *  该方法将会修改当前绘图上下文的可视区域.
  *  当调用这个方法之后, 会导致接下来所有的渲染
  *  操作, 只会在剪切下来的区域内进行, 区域外的
  *  内容将不会被渲染.
  *  如果你希望执行接下来的绘图时, 删除剪切区域,
  *  那么你必须在调用该方法前, 先使用 CGContextSaveGState 方法
  *  保存当前的绘图状态, 当你不再需要这个剪切区域
  *  的时候, 你只需要使用 CGContextRestoreGState 方法
  *  来恢复之前保存的绘图状态就可以了.
  * @param blendMode: 混合模式决定了如何和
  *                   已经存在的被渲染过的内容进行合成
  * @param alpha: 填充路径时的透明度
  */
- (void)addClip;
/**
  *  该方法返回一个布尔值, 当曲线的覆盖区域包含
  * 指定的点(内部点), 则返回 YES, 否则返回 NO. 
  * Note: 如果当前的路径是一个开放的路径, 那么
  *       就算指定点在路径覆盖范围内, 该方法仍然会
  *       返回 NO, 所以如果你想判断一个点是否在一个
  *       开放路径的范围内时, 你需要先Copy一份路径,
  *       并调用 -(void)closePath; 将路径封闭, 然后
  *       再调用此方法来判断指定点是否是内部点.
  * @param point: 指定点.
  */
- (BOOL) containsPoint:(CGPoint)point;
/**
  * 该方法将会直接对路径中的所有点进行指定的放射
  * 变换操作. 
  */
- (void)applyTransform:(CGAffineTransform)transform;

UIBezierPath实例

UIBezierPath的使用方法

1.创建一个Bezier path对象。
2.使用方法moveToPoint:去设置初始线段的起点。
3.添加line或者curve去定义一个或者多个subpaths。
4.改变UIBezierPath对象跟绘图相关的属性

画矩形

//设置线条颜色
UIColor *color = [UIColor redColor];
[color set];
//创建UIBezierPath
UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(60, 60, 70, 70)];
//设置线条宽度
path.lineWidth = 5.0;
//端点样式
path.lineCapStyle  = kCGLineCapRound;
//拐角样式
path.lineJoinStyle = kCGLineCapRound;
//    //根据坐标填充颜色
//    [path fill];
//根据坐标连线
[path stroke];

画圆或者椭圆

//设置线条颜色
UIColor *color = [UIColor redColor];
[color set];
//创建UIBezierPath
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(60, 60, 70, 70)];
//根据坐标连线
[path stroke];

画扇形

圆的坐标
//设置线条颜色
UIColor *color = [UIColor redColor];
[color set];
//创建UIBezierPath
UIBezierPath *path = [UIBezierPath bezierPath];
//设置起点
[path moveToPoint:CGPointMake(100, 100)];
//设置路径
[path addArcWithCenter:CGPointMake(100, 100)
                    radius:66.f
                startAngle:0
                  endAngle:M_PI_2*3
                 clockwise:YES];
//闭合路径
[path closePath];
//填充颜色
[path fill];

画不规则图形

//设置线条颜色
UIColor *color = [UIColor redColor];
[color set];
//创建UIBezierPath
UIBezierPath *path = [UIBezierPath bezierPath];
//设置起始点
[path moveToPoint:CGPointMake(20, 20)];
//添加直线
[path addLineToPoint:CGPointMake(20, 150)];
[path addLineToPoint:CGPointMake(100, 130)];
[path addLineToPoint:CGPointMake(150, 60)];
//闭合路径
[path closePath];
//根据坐标连线
[path stroke];

画弧

//设置线条颜色
UIColor *color = [UIColor redColor];
[color set];
//创建UIBezierPath
UIBezierPath *path = [UIBezierPath bezierPath];
//设置路径
[path addArcWithCenter:CGPointMake(100, 100)
                radius:66.f
            startAngle:0
              endAngle:M_PI_2
             clockwise:YES];
    
//根据坐标连线
[path stroke];

画三阶贝塞尔曲线

贝塞尔曲线坐标
//设置线条颜色
UIColor *color = [UIColor redColor];
[color set];
//创建UIBezierPath
UIBezierPath *path = [UIBezierPath bezierPath];
//设置起始点
[path moveToPoint:CGPointMake(0, 100)];
//设置路径
[path addCurveToPoint:CGPointMake(200, 100) controlPoint1:CGPointMake(50, 50) controlPoint2:CGPointMake(150, 150)];
//根据坐标连线
[path stroke];

参考:完整项目资料下载

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

推荐阅读更多精彩内容