美文网首页UIBezierPath
iOS绘图和打印编程指导(三)-使用UIBezierPath来绘

iOS绘图和打印编程指导(三)-使用UIBezierPath来绘

作者: 陵无山 | 来源:发表于2018-10-29 17:38 被阅读45次

    iOS 3.2后, 你就可以使用UIBezierPath类创建向量路径. 类UIBezierPath是使用OC对Core Graphic中的绘图特性的一个封装. 你可以使用这个类去定义简单的形状, 比如想椭圆和矩形, 和一些比较包含直线和曲线的复杂形状. 然后你可以使用这些路径对象在界面上绘制图形. 你可以对路径描边, 填充颜色, 或者两者都有. 你也可以使用路径来裁剪绘画上下文中的区域, 裁剪的区域之后用来修改接下来的绘图操作.

    Bezier Path基础


    UIBezierPath对象是对CGPathRef类型数据的包装. 路径(path)是使用线段和曲线构成的基于向量的形状. 你可以使用线段构建矩形, 多边形, 使用曲线构建圆弧, 圆, 和其他复杂的曲线图形. 而线是由坐标系中的点构成, 而这些点是由绘画命令来控制的.

    路径中的部分线段和曲线构成了子路径. 子路径中的结束点是下个子路径的起始点. 单个UIBezierPath对象可能包含多个子路径, 这些子路径通过命令moveToPoint:来区分. 该命令可以是画笔移动到一个新位置.

    路径的创建和使用是分开的, 构建路径的是绘图过程中的第一部分, 下面是构建路径步骤:

    1. 创建路径对象
    2. 设置路径对象(UIBezierPath)一些绘图属性, 比如lineWidth(线宽), lineJoinStyle(连接风格)等路径绘制的设置, 或者使用属性usesEvenOddFillRule来填充路径. 这些属性设置会应用到整个路径.
    3. 使用moveToPoint:命令来开始一个子路径
    4. 通过直线和曲线来构建一个子路径
    5. 调用方法closePath来关闭路径, 会将路径中最后一部分的结束点和第一部分的起始点连接起来, 该过程是可选的.
    6. 重复步骤3,4,5来添加更多子路径, 该过程是可选的.

    当你构建路径时, 要相对于原点(0,0)合理安排路径中的点, 这样在后续移动路径的时候比较方便. 在绘制路径时, 点的位置和当前坐标系中的一样. 如果你的路径时相对于原点进行定向的, 当你想改变路径的位置时, 只需要对当前绘图上下文做一次仿射变换. 为啥修改绘图上下文而不是直接修改路径本身呢? 修改绘图上下文的好处就是通过绘图状态的存储和恢复操作,可以取消上次的修改.

    你可以使用strokefill方法来绘制路径. 渲染过程涉及使用路径对象的属性对线条和曲线进行光栅化. 光栅化过程不修改路径对象本身. 因此, 可以在当前上下文或其他上下文中多次渲染同一路径对象.

    往路径中添加线条和多边形(polygon)


    线段和多边形这些简单的图形是通过moveToPoint:addLineToPoint:方法来逐点构建的. moveToPoint:方法设置了图形的起点, 从起点出发调用addLineToPoint:方法往图形中添加一条线. 通过这方式, 你可以连续移动点来添加一系列线段.

    代码3-1展示了使用代码创建一个五边形. 代码中, 先创建设置一个初始点, 然后连续添加四条线, 再调用closePath方法, 自动生成第五条线(连接第四条线的end-point到初始点). 该路径绘制的图形如图3-1所示.
    代码清单3-1 创建一个五边形

    UIBezierPath *aPath = [UIBezierPath bezierPath];
     
    // Set the starting point of the shape.
    [aPath moveToPoint:CGPointMake(100.0, 0.0)];
     
    // Draw the lines.
    [aPath addLineToPoint:CGPointMake(200.0, 40.0)];
    [aPath addLineToPoint:CGPointMake(160, 140)];
    [aPath addLineToPoint:CGPointMake(40.0, 140)];
    [aPath addLineToPoint:CGPointMake(0.0, 40.0)];
    [aPath closePath];
    
    使用UIBezierPath来绘制图形

    使用closePath方法来关闭路径有一个好处是, 在绘制多变型是不需要绘制最后一条边, 因为该方法会自动绘制一条起始点到最后一点的线.

    往路径中添加圆弧(Arcs)


    使用UIBezierPath类的bezierPathWithArcCenter:radius:startAngle:endAngle:clockwise:方法可以绘制一段弧. 从该方法的参数(原点, 半径, 起始角度和结束角度, 时针方向),我们可以确定该如何画好圆弧. 图3-2, 展示了这些参数如何确定一段圆弧, 该弧是顺时针方法. 代码3-2,展示了创建图3-2中圆弧的代码.

    图3-2 默认坐标系中的圆弧
    代码清单3-2 创建一段圆弧路径
    // pi is approximately equal to 3.14159265359.
    #define   DEGREES_TO_RADIANS(degrees)  ((pi * degrees)/ 180)
     
    - (UIBezierPath *)createArcPath {
       UIBezierPath *aPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(150, 150)
                               radius:75
                               startAngle:0
                               endAngle:DEGREES_TO_RADIANS(135)
                               clockwise:YES];
       return aPath;
    }
    

    如果你想将一段圆弧加入另一路径中, 那么请直接修改路径的CGPathRef类型的对象. 关于如何使用Core Graphic函数修改路径的方法下面内容会提到.

    使用Core Graphic函数来修改路径


    UIBezierPath支持往路径中添加三次和二次贝塞尔曲线. 曲线由起点和终点确定曲线的首尾. 曲线弯曲在起点和终点的切线之间, 弯曲程度由控制点决定, 你也可以增加一个或多个控制点. 图3-3展示了两种类型的曲线间的控制点和曲线弯曲的关系, 底层的数学逻辑请看Wikipedia

    图3-3 贝塞尔曲线

    往路径中添加贝塞尔曲线可以使用以下方法:

    • 二次曲线:addCurveToPoint:controlPoint1:controlPoint2:
    • 三次曲线:addQuadCurveToPoint:controlPoint:

    因为曲线在当前点的基础上添加的, 并且以该点作为曲线的起始点. 所以在调用上面两个方法之前, 需要设置当前点.

    创建椭圆和矩形路径


    UIBezierPath提供了bezierPathWithRect:bezierPathWithOvalInRect:方法来创建矩形和椭圆形. 这个两个方法都是创建一个新的path对象. 你可以直接使用返回的对象或者往该path中加入更多图形.

    如果你想往现存的path中添加一个矩形, 那么可以使用moveToPoint, addLineToPoint:, closePath方法来创建矩形, 就像创建多边形一样. 如果你想往现存的path中添加一个椭圆, 最简单的方法是使用Core Graphic. 尽管你可以使用addQuadCurveToPoint:controlPoint:创建一个类椭圆, 但是CGPathAddEllipseInRect函数更加简单实用.

    使用CoreGraphic来修改路径


    UIBezierPath其实是CGPathRef数据类型, 以及和该路径相关的绘画属性的包装类. 虽然通常使用UIBezierPath的方法添加线段和曲线段, 但是该类还开了一个CGPath属性, 你可以使用它直接修改底层路径数据类型. 当你希望使用Core Graphic框架的函数来修改路径时, 可以使用此属性.

    有两种方法可修改UIBezierPath对象相关的路径. 你可以完全使用Core Graphic函数修改路径, 也可以将Core Graphic函数和UIBezierPath方法结合使用. 在某些方面, 完全使用Core Graphic函数修改路径更容易. 你可以创建一个可变的CGPathRef数据类型, 并调用你需要的任何函数来修改路径信息. 完成后, 将路径对象分配给相应的UIBezierPath对象, 如代码3-3所示.
    代码清单3-3 将一个新CGPathRef设置到UIBezierPath对象

    // Create the path data.
    CGMutablePathRef cgPath = CGPathCreateMutable();
    CGPathAddEllipseInRect(cgPath, NULL, CGRectMake(0, 0, 300, 300));
    CGPathAddEllipseInRect(cgPath, NULL, CGRectMake(50, 50, 200, 200));
     
    // Now create the UIBezierPath object.
    UIBezierPath *aPath = [UIBezierPath bezierPath];
    aPath.CGPath = cgPath;
    aPath.usesEvenOddFillRule = YES;
     
    // 使用完后记得release掉
    CGPathRelease(cgPath);
    

    如果你选择混合使用Core Graphic函数和UIBezierPath方法, 那么必须小心路径对象在Core Graphic和UIBezierPath间的来回移动. 因为UIBezierPath对象拥有其底层CGPathRef数据类型,所以不能简单地获取该类型并直接对其进行修改。相反,您必须创建一个可变副本,修改副本,然后将副本分配回CGPath属性,如清单3-4所示
    代码清单3-4 混合使用Core Graphic和UIBezierPath

    UIBezierPath *aPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, 300, 300)];
     
    // Get the CGPathRef and create a mutable version.
    CGPathRef cgPath = aPath.CGPath;
    CGMutablePathRef  mutablePath = CGPathCreateMutableCopy(cgPath);
     
    // Modify the path and assign it back to the UIBezierPath object.
    CGPathAddEllipseInRect(mutablePath, NULL, CGRectMake(50, 50, 200, 200));
    aPath.CGPath = mutablePath;
     
    // Release both the mutable copy of the path.
    CGPathRelease(mutablePath);
    

    渲染贝塞尔路径中的内容


    当创建完一个UIBezierPath对象后, 你可以使用strokefill方法在当前绘图上下文中渲染该路径. 在调试前面方法之前, 这里还需要做一些其他操作来确保路径准确绘制到上下文中:

    • 使用UIColor中的方法来设置想要的strokeColor和fillColor

    • 将该图形放到目标视图中合适的位置.
      如果创建了相对于点(0,0)的路径,则可以对当前绘图上下文应用适当的仿射转换。例如,要从点(10,10)开始绘制形状,需要调用CGContextTranslateCTM函数,并为水平和垂直位移值指定10。调整图形上下文(与调整路径对象中的点相反)是首选的,因为通过保存和恢复以前的图形状态,可以更容易地撤消更改。

    • 更新路径的绘图属性. 设置UIBezierPath对象的绘图属性, 会将上下文中的绘图属性覆盖掉.

    代码3-5展示在视图中绘制椭圆的drawRect:方法实现. 因为填充操作直接绘制到路径边界,所以该方法在stroke路径之前填充路径。这样可以防止填充颜色遮蔽半行的线。
    代码清单3-5 在view中绘制路径

    - (void)drawRect:(CGRect)rect {
        // Create an oval shape to draw.
        UIBezierPath *aPath = [UIBezierPath bezierPathWithOvalInRect:
                                    CGRectMake(0, 0, 200, 100)];
     
        // Set the render colors.
        [[UIColor blackColor] setStroke];
        [[UIColor redColor] setFill];
     
        CGContextRef aRef = UIGraphicsGetCurrentContext();
     
        // If you have content to draw after the shape,
        // save the current state before changing the transform.
        //CGContextSaveGState(aRef);
     
        // Adjust the view's origin temporarily. The oval is
        // now drawn relative to the new origin point.
        CGContextTranslateCTM(aRef, 50, 50);
     
        // Adjust the drawing options as needed.
        aPath.lineWidth = 5;
     
        // Fill the path before stroking it so that the fill
        // color does not obscure the stroked line.
        [aPath fill];
        [aPath stroke];
     
        // Restore the graphics state before drawing any other content.
        //CGContextRestoreGState(aRef);
    }
    

    在路径上执行点击检测


    若要确定在路径的填充部分是否发生触摸事件,可以UIBezierPath的contiansPoint:方法。此方法针对路径对象中的所有封闭子路径测试指定的点,如果位于或位于这些子路径中的任何一个子路径,则返回“YES”。

    重要: 方法containsPoint:和Core Graphic的hit-testing函数必须依赖关闭的路径. 如果使用打开的路径那么这些方法会返回NO. 如果你想对开发的路径进行hit-test测试那么你必须先创建路径的副本, 然后关闭副本路径, 在使用副本路径进行测试.

    如果你想对路径的stroke部分进行hit-test测试, 那么你必须使用CoreGraphic的函数. 函数CGContextPathContainsPoint可以让你对路径的stroke部分进行hit-test也可对fill部分测试. 代码3-6中展示了测试一点是否和路径重合. 参数inFill控制方法是否对fill部分测试. 进行测试的路径必须包含关闭的子路径.
    代码3-6 测试路径中的点

    - (BOOL)containsPoint:(CGPoint)point onPath:(UIBezierPath *)path inFillArea:(BOOL)inFill {
       CGContextRef context = UIGraphicsGetCurrentContext();
       CGPathRef cgPath = path.CGPath;
       BOOL    isHit = NO;
     
       // Determine the drawing mode to use. Default to
       // detecting hits on the stroked portion of the path.
       CGPathDrawingMode mode = kCGPathStroke;
       if (inFill) {
          // Look for hits in the fill area of the path instead.
          if (path.usesEvenOddFillRule)
             mode = kCGPathEOFill;
          else
             mode = kCGPathFill;
       }
     
       // Save the graphics state so that the path can be
       // removed later.
       CGContextSaveGState(context);
       CGContextAddPath(context, cgPath);
     
       // Do the hit detection.
       isHit = CGContextPathContainsPoint(context, point, mode);
       CGContextRestoreGState(context);
       return isHit;
    }
    

    相关文章

      网友评论

        本文标题:iOS绘图和打印编程指导(三)-使用UIBezierPath来绘

        本文链接:https://www.haomeiwen.com/subject/wqpqtqtx.html