UIBezierPath 解析

github: https://github.com/wangjinshan/IJSPhotoSDK

初始化方法解析

  • 该方法将会创建一个闭合路径, 起始点是 rect 参数的origin, 并且按照顺时针方向添加直线, 最终形成矩形
 [UIBezierPath bezierPathWithRect:CGRectMake(10, 600, 100, 100)];   
  • 绘制出一个近似椭圆的形状. 如果 rect 参数指定了一个正方形, 那么该 UIBezierPath 对象将会描述一个圆形
[UIBezierPath bezierPathWithOvalInRect:CGRectMake(10, 10, 50, 100)]; 
  • 该方法会顺时针方向连续绘制直线和曲线. 当 rect 为正方形时且 cornerRadius 等于边长一半时, 则该方法会描述一个圆形路径
[UIBezierPath bezierPathWithRoundedRect:CGRectMake(10, 300, 70, 50) cornerRadius:20]; 
  • 绘制一个一脚为弧形的矩形 UIRectCorner 枚举类型, byRoundingCorners: 指定矩形的哪个角变为圆角 cornerRadii: 矩形的圆角半径
 [UIBezierPath bezierPathWithRoundedRect:CGRectMake(10, 10, 50, 50) byRoundingCorners:UIRectCornerBottomLeft cornerRadii:CGSizeMake(50, 25)]; 
  • 该方法会创建出一个开放路径, 创建出来的圆弧是圆的一部分. 在默认的坐标系统中, 开始角度 和 结束角度 都是基于单位圆. 调用这个方法之后, currentPoint 将会设置为圆弧的结束点.
    如: 指定其实角度为0, 指定结束角度为π, 设置 clockwise 属性为 YES, 将会绘制出圆的下半部分.然而当我们不修改起始角度 和 结束角度, 我们仅仅将 clockwise 角度设置为 NO, 则会绘制出来一个圆的上半部分
    center: 圆心 radius: 半径 startAngle: 起始角度 endAngle: 结束角度 clockwise: 是否顺时针绘制
[UIBezierPath bezierPathWithArcCenter:CGPointMake(100, 100) radius:50 startAngle:0 endAngle:M_PI clockwise:NO]; 
  • 通过一个 CGPath, 创建并且返回一个新的 UIBezierPath 对象
CGPathRef path = CGPathCreateWithRect(CGRectMake(100, 100, 90, 50), nil);
 [UIBezierPath bezierPathWithCGPath:path]; 

路径追加

  • 设置起点
    如果当前有正在绘制的子路径, 该方法则会隐式的结束当前路径, 并将 currentPoint 设置为指定点. 当上一条子路径被终止, 该方法实际上并不会去闭合上一条子路径. 所以上一条自路径的起始点 和 结束点并没有被链接.
    对于大多数构造路径相关的方法而言, 在你绘制直线或曲线之前, 需要先调用这个方法, point: 当前坐标系统中的某一点
[path moveToPoint:CGPointMake(50, 280)]
  • 添加直线
 [path1 addLineToPoint:CGPointMake(200, 50)];    
    • 该方法将会从 currentPoint 到 指定的 endPoint 追加一条三次贝塞尔曲线. 三次贝塞尔曲线的弯曲由两个控制点来控制. 如下图所示 Note: 调用该方法前, 你必须先设置 currentPoint, 如果路径为空, 并且尚未设置 currentPoint, 调用该方法则不会产生任何效果. 当添加完贝塞尔曲线后, 该方法将会自动更新 currentPoint 为 指定的结束点endPoint: 终点 controlPoint1: 控制点1 controlPoint2: 控制点2
 [path1 addCurveToPoint:CGPointMake(300, 300) controlPoint1:CGPointMake(50, 70) controlPoint2:CGPointMake(150, 70)];
  • 该方法将会从 currentPoint 到 指定的 endPoint 追加一条二次贝塞尔曲线 currentPoint、endPoint、controlPoint 三者的关系最终定义了二次贝塞尔曲线的形状. 二次贝塞尔曲线的弯曲由一个控制点来控制. 如下图所示 Note: 调用该方法前, 你必须先设置 currentPoint, 如果路径为空,并且尚未设置 currentPoint, 调用该方法则不会产生任何效果. 当添加完贝塞尔曲线后, 该方法将会自动更新 currentPoint 为指定的结束点endPoint: 终点 controlPoint: 控制点
[path3 addQuadCurveToPoint:CGPointMake(250, 300) controlPoint:CGPointMake(100, 50)]; 

该方法将会从 currentPoint 添加一条指定的圆弧. center: 圆心 radius: 半径
startAngle: 起始角度 endAngle: 结束角度 clockwise: 是否顺时针绘制

 [path3 addArcWithCenter:CGPointMake(200, 400) radius:50 startAngle:90 endAngle:180 clockwise:NO];
  • 该方法将会从 currentPoint 到子路经的起点 绘制一条直线, 以此来关闭当前的自路径. 紧接着该方法将会更新 currentPoint为 刚添加的这条直线的终点, 也就是当前子路经的起点.
 [path3 closePath];   // 关闭路径
closepath.png
* 删除 UIBezierPath 对象中的所有点, 效果也就等同于删除了所有子路经
[path3 removeAllPoints];
 [path3 appendPath:path7];   // 路径追加
获取这个属性, 你将会获得一个不可变的 CGPathRef 对象, 他可以传入 CoreGraphics 提供的函数中你可以是用 CoreGraphics 框架提供的方法创建一个路径, 并给这个属性赋值, 当时设置了一个新的路径后, 这个将会对你给出的路径对象进行 Copy 操作

   @property(nonatomic) CGPathRef CGPath;
该属性的值, 将会是下一条绘制的直线或曲线的起始点.
 如果当前路径为空, 那么该属性的值将会是 CGPointZero
 
@property(nonatomic, readonly) CGPoint currentPoint;

线宽属性定义了 UIBezierPath 对象中绘制的曲线规格. 默认为: 1.0
@property(nonatomic) CGFloat lineWidth;
该属性应用于曲线的终点和起点. 该属性在一个闭合子路经中是无效果的. 默认为: kCGLineCapButt
@property(nonatomic) CGLineCap lineCapStyle;
     * kCGLineCapButt,      // 方
     * kCGLineCapRound,   //圆弧状
     * kCGLineCapSquare    //方
UIBezierPath 解析_第1张图片
image.png
曲线连接点样式
@property(nonatomic) CGLineJoin lineJoinStyle;
UIBezierPath 解析_第2张图片
image.png
两条线交汇处内角和外角之间的最大距离, 只有当连接点样式为 kCGLineJoinMiter
时才会生效,最大限制为10
我们都知道, 两条直线相交时, 夹角越小, 斜接长度就越大.
该属性就是用来控制最大斜接长度的.
当我们设置了该属性, 如果斜接长度超过我们设置的范围, 
则连接处将会以 kCGLineJoinBevel 连接类型进行显示.
  
@property(nonatomic) CGFloat miterLimit;
UIBezierPath 解析_第3张图片
image.png
 该属性用来确定渲染曲线路径的精确度.
  该属性的值用来测量真实曲线的点和渲染曲线的点的最大允许距离.
  值越小, 渲染精度越高, 会产生相对更平滑的曲线, 但是需要花费更
  多的计算时间. 值越大导致则会降低渲染精度, 这会使得渲染的更迅
  速. flatness 的默认值为 0.6.
  Note: 大多数情况下, 我们都不需要修改这个属性的值. 然而当我们
  希望以最小的消耗去绘制一个临时的曲线时, 我们也许会临时增
 大这个值, 来获得更快的渲染速度.


@property(nonatomic) CGFloat flatness;
设置为 YES, 则路径将会使用 基偶规则 (even-odd) 进行填充.
设置为 NO,  则路径将会使用 非零规则 (non-zero) 规则进行填充.
@property(nonatomic) BOOL usesEvenOddFillRule;
  • 不自交: 一个多边形, 仅顶点处连接, 而在画布内没有其他的公共交点.
  • 自相交: 一个多边形, 除了顶点连接外, 在画布内还有其他的公共交点.
    判断一个点是否在自相交多边形内,用到一下判断方法
  • [ ] 奇偶原则: 从路径覆盖范围内的任意一点做一条射线(确保这条射线的长度要比路径覆盖范围要大) , 如果与该射线相交的边的数量为奇数, 则该点是路径的内部点, 反之该点则是路径的外部点
    ![基偶原则]


    UIBezierPath 解析_第4张图片
    image.png
  • [ ] 非零环绕数原则: 在我们脑海中定义一个变量, 比如叫:count, 然后从路径覆盖范围内的任意一点做一条射线(确保这条射线的长度要比路径覆盖范围要大). 然后我们对每一条和该射线相交的路径进行统计, 统计规则是这样的: 当路径是从右向左(顺时针)穿过射线的时候, count++, 当路径是从左向右(逆时针)穿过射线的时候, count--. 当我们统计完所有相交的路径后, 如果 count 不为0, 则该点是内部点, 该点所在的封闭区域需要填充, 反之该点则是路径的外部点
    ![非零环绕数原则]


    UIBezierPath 解析_第5张图片
    image.png

从图中看的很清晰, 从左图中的红点开始, 绘制了一条射线, 图中经过了两条路径, 这两条路径的方向都是从左向右穿过射线的, 每经过一条从左向右穿过射线的路径, count 会减一, 那么最终count 为 -2, 通过非零环绕数原则判定, 该点为内部点, 所以该点所在的区域进行了填充. 右图射线同样经过了两条路径, 但是这两条路径的方向一个是从左向右穿过射线的, 另一个是从右向左穿过射线的,那么最终count 为0, 通过非零环绕数原则判定, 该点为外部点, 所以该点所在的区域不进行填充操作.
验证:

// 测试
-(void)test10
{
    UIBezierPath *path = [UIBezierPath bezierPath];
    path.usesEvenOddFillRule = NO;
    [path moveToPoint:CGPointMake(300, 300)];
    [path addArcWithCenter:CGPointMake(200, 300) radius:100 startAngle:0 endAngle:M_PI * 2 clockwise:YES];
    [path moveToPoint:CGPointMake(250, 300)];
    [path addArcWithCenter:CGPointMake(200, 300) radius:50 startAngle:0 endAngle:M_PI * 0.001 clockwise:NO];
    [path moveToPoint:CGPointMake(210, 300)];
    [path addArcWithCenter:CGPointMake(200, 300) radius:10 startAngle:0 endAngle:M_PI * 0.001 clockwise:NO];
    [[UIColor redColor]set];
    [path fill];
}
//  注意角度划圆
-(void)test11
{
    UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(200, 300) radius:200 startAngle:0 endAngle:M_PI * 2 clockwise:NO];
    [path addArcWithCenter:CGPointMake(200, 300) radius:100 startAngle:0 endAngle:M_PI * 2 clockwise:YES];
    [path addArcWithCenter:CGPointMake(200, 300) radius:50 startAngle:0 endAngle:M_PI * 2 clockwise:YES];
   
     path.usesEvenOddFillRule = NO; // 默认值,可以不写
    [[UIColor redColor] set];
    [path fill];
}

综合实例:

UIBezierPath 解析_第6张图片
image.png

从图中看出, 蓝色的射线经过的三条路径, 最终的结果count 为 1, 所以蓝色点所在的封闭区域是需要填充的.

画虚线

方法注释:

  • pattern: 该属性是一个 C 语言的数组, 其中每一个元素都是 CGFloat 数组中的元素代表着线段每一部分的长度, 第一个元素代表线段的第一条线,第二个元素代表线段中的第一个间隙. 这个数组中的值是轮流的. 来解释一下 例子: 声明一个数组 CGFloat dash[] = @{3.0, 1.0}; 这意味着绘制的虚线的第一部分长度为3.0, 第一个间隙长度为1.0, 虚线的 第二部分长度为3.0, 第二个间隙长度为1.0. 以此类推. count: 这个参数是 pattern 数组的个数 phase: 这个参数代表着, 虚线从哪里开始绘制. 举个例子: 这是 phase 为 6. pattern[] = @{5, 2, 3, 2}; 那么虚线将会 第一个间隙的中间部分开始绘制, 如果不是很明白就请继续往下看,
CGFloat dashLineConfig2[] = {8.0, 4.0, 16.0, 8.0};
    [path1 setLineDash: dashLineConfig2
                 count: 2
                 phase: 12];

该方法可以重新获取之前设置过的虚线样式. Note: pattern 这个参数的容量必须大于该方法返回数组的容量.如果无法确定数组的容量, 那么可以调用两次该方法, 第一次 调用该方法的时候, 传入 count 参数, 然后在用 count 参数 来申请 pattern 数组的内存空间. 然后再第二次正常的调用该方法

- (void)getLineDash:(CGFloat *)pattern 
              count:(NSInteger *)count
              phase:(CGFloat *)phase;

  • 该方法当前的填充颜色 和 绘图属性对路径的封闭区域进行填充.如果当前路径是一条开放路径, 该方法将会隐式的将路径进行关闭后进行填充该方法在进行填充操作之前, 会自动保存当前绘图的状态, 所以我们不需要自己手动的去保存绘图状态了.
- (void)fill;

该方法当前的填充颜色 和 绘图属性 (外加指定的混合模式 和 透明度) 对路径的封闭区域进行填充. 如果当前路径是一条开放路径, 该方法将会隐式的将路径进行关闭后进行填充,该方法在进行填充操作之前, 会自动保存当前绘图的状态, 所以我们不需要自己手动的去保存绘图状态了.
blendMode: 混合模式决定了如何和已经存在的被渲染过的内容进行合成
alpha: 填充路径时的透明度

- (void)fillWithBlendMode:(CGBlendMode)blendMode 
                    alpha:(CGFloat)alpha;
- (void)stroke;  //绘制路径
 blendMode: 混合模式决定了如何和已经存在的被渲染过的内容进行合成
  alpha: 填充路径时的透明度
- (void)strokeWithBlendMode:(CGBlendMode)blendMode
                      alpha:(CGFloat)alpha;

该方法将会修改当前绘图上下文的可视区域.当调用这个方法之后, 会导致接下来所有的渲染
操作, 只会在剪切下来的区域内进行, 区域外的内容将不会被渲染.如果你希望执行接下来的绘图时, 删除剪切区域,那么你必须在调用该方法前, 先使用 CGContextSaveGState 方法保存当前的绘图状态, 当你不再需要这个剪切区域的时候, 你只需要使用 CGContextRestoreGState 方法来恢复之前保存绘图状态就可以了.blendMode: 混合模式决定了如何和已经存在的被渲染过的内容进行合成,alpha: 填充路径时的透明度

- (void)addClip;

该方法返回一个布尔值, 当曲线的覆盖区域包含指定的点(内部点), 则返回 YES, 否则返回 NO.
Note: 如果当前的路径是一个开放的路径, 那么就算指定点在路径覆盖范围内, 该方法仍然会返回NO, 所以如果你想判断一个点是否在一个开放路径的范围内时, 你需要先Copy一份路径,并调用 -(void)closePath; 将路径封闭, 然后再调用此方法来判断指定点是否是内部点.@param point: 指定点.

- (BOOL) containsPoint:(CGPoint)point;

检测当前路径是否绘制过直线或曲线. Note: 记住, 就算你仅仅调用了 moveToPoint 方法那么当前路径也被看做不为空.

@property (readonly, getter=isEmpty) BOOL empty;

该属性描述的是一个能够完全包含路径中所有点的一个最小的矩形区域. 该区域包含二次贝塞尔 曲线和三次贝塞尔曲线的控制点.

@property (nonatomic, readonly) CGRect bounds;

该方法将会直接对路径中的所有点进行指定的放射 变换操作. 一般是平移

- (void)applyTransform:(CGAffineTransform)transform;

为这条路径制作一个反转路径

- (UIBezierPath *) bezierPathByReversingPath;
// 方向反转解析
-(void)text3
{
    // 1. 随便画一个路径出来.
    UIBezierPath *path = [UIBezierPath bezierPath];
    [path moveToPoint: CGPointMake(10, 10)];
    [path addLineToPoint: CGPointMake(80, 40)];
    [path addLineToPoint: CGPointMake( 40,  80)];
    [path addLineToPoint: CGPointMake(40, 40)];
    path.lineWidth = 3;
    
    // 2. 为这条路径制作一个反转路径
    UIBezierPath *reversingPath = [path bezierPathByReversingPath];
    reversingPath.lineWidth = 3;
    
    // 3. 为了避免两条路径混淆在一起, 我们为第一条路径做一个位移
    CGAffineTransform transform = CGAffineTransformMakeTranslation(200, 0);
    [path applyTransform: transform];
    
    // 4. 两条路径分别添加一条直接到 self.center
    [path addLineToPoint: CGPointMake(self.frame.size.width*0.5, self.frame.size.height*0.5)];
    [reversingPath addLineToPoint: CGPointMake(self.frame.size.width*0.5, self.frame.size.height*0.5)];
    
    // 5. 设置颜色, 并绘制路径
    [[UIColor redColor] set];
    [path stroke];
    
    [[UIColor greenColor] set];
    [reversingPath stroke];  
}
UIBezierPath 解析_第7张图片
image.png

绿色是红色的复制品,知识做了平移,但是从首尾画线来看是反转路径

  • 虚线
  1. @param pattern: 这个参数说白了就是虚线的规格, 你让他多长他就多长, 你让他有多大的间距他就有多大的间距. 是一个 C 语言的数组.
  2. @param count: 官方文档上说就是 pattern 数组的个数. 那你就直接给一个数组的个数就完了, 何必给自己添麻烦呢.
  3. @param phase: 从哪个位置开始绘制虚线, 也就是偏移量, 你给个20, 那他就从20开始绘制虚线

- (void)text1
{
    // 1. 先创建三条路径, 有对比更有助于理解
    UIBezierPath *path = [UIBezierPath bezierPath];
    [path moveToPoint: CGPointMake(80, 40)];
    [path addLineToPoint: CGPointMake(self.frame.size.width - 40, 40)];
    path.lineWidth = 2;

    UIBezierPath *path1 = [UIBezierPath bezierPath];
    [path1 moveToPoint: CGPointMake(80, 80)];
    [path1 addLineToPoint: CGPointMake(self.frame.size.width - 40, 80)];
    path1.lineWidth = 2;
    
    
    UIBezierPath *path2 = [UIBezierPath bezierPath];
    [path2 moveToPoint: CGPointMake(80, 120)];
    [path2 addLineToPoint: CGPointMake(self.frame.size.width - 40, 120)];
    path2.lineWidth = 2;
    
    // 2.  这部分是配置三条路径虚线的规格, 重点主要是这部分.
    CGFloat dashLineConfig[] = {8.0, 4.0};   // 长度和间隔
    [path setLineDash: dashLineConfig
                count: 3
                phase: 0];
    
    
    CGFloat dashLineConfig1[] = {8.0, 4.0, 16.0, 8.0};
    [path1 setLineDash: dashLineConfig1
                 count: 4
                 phase: 0];
    
    
    CGFloat dashLineConfig2[] = {8.0, 4.0, 16.0, 8.0};
    [path2 setLineDash: dashLineConfig2
                 count: 4
                 phase: 12];
注释:  dashLineConfig2这个数组描述的是虚线绘制的样式,就是虚线展示的模式,count: 表示数组重复个数,注意 count个数等于数组的长度, phase表示偏移量,比如上面的  phase = 12, 就是向左边便宜 12 ( 8 - 12)的距离

    // 3. 绘制
    [[UIColor orangeColor] set];
    [path stroke];
    [path1 stroke];
    [path2 stroke];
}

UIBezierPath 解析_第8张图片
image.png

多阶贝塞尔曲线的展示

UIBezierPath 解析_第9张图片
1维图.gif
UIBezierPath 解析_第10张图片
2维图.gif
UIBezierPath 解析_第11张图片
3维图.gif
UIBezierPath 解析_第12张图片
4维图.gif
5维图.gif

你可能感兴趣的:(UIBezierPath 解析)