iOS下的图形绘制

Author Avatar
XibHe 4月 24, 2017
  • 在其它设备中阅读本文章

图形绘制

iOS系统本身提供了两套绘图的框架,即UIBezierPath Core Graphics。而前者所属UIKit,其实是对Core Graphics框架关于path的进一步封装,所以使用起来比较简单。但是毕竟Core Graphics更接近底层,所以它更加强大。

UIBezierPath

UIKit中的UIBezierPath是Core Graphics框架关于path的一个封装。可以创建基于矢量的路径,例如椭圆或者矩形,或者有多个直线和曲线段组成的形状。我们可以用moveToPoint: 和 addLineToPoint:方法去构建。moveToPoint:设置我们想要创建形状的起点。从这点开始,我们可以用方法addLineToPoint:去创建一个形状的线段。我们可以连续的创建line,每一个line的起点都是先前的终点,终点就是指定的点。closePath可以在最后一个点和第一个点之间画一条线段。

- (void)drawRect:(CGRect)rect
{
    UIColor *color = [UIColor colorWithRed:0 green:0.7 blue:0 alpha:1];
    [color set];

    UIBezierPath* aPath = [UIBezierPath bezierPath];
    aPath.lineWidth = 5.0;

    aPath.lineCapStyle = kCGLineCapRound;
    aPath.lineJoinStyle = kCGLineCapRound;

    // 起点
    [aPath moveToPoint:CGPointMake(100.0, 0.0)];

    // 绘制线条
    [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];//第五条线通过调用closePath方法得到的

    //根据坐标点连线
    [aPath stroke];
    [aPath fill];
}

UIBezierPath+CAShapeLayer

CAShapeLayer 继承自 CALayer ,因此,可使用 CALayer 的所有属性。但是, CAShapeLayer 需要和贝塞尔曲线配合使用才有意义。
查看官网说明:

/* The shape layer draws a cubic Bezier spline in its coordinate space.

  • The spline is described using a CGPath object and may have both fill
  • and stroke components (in which case the stroke is composited over
  • the fill). The shape as a whole is composited between the layer’s
  • contents and its first sublayer.
    */

这里是说 CAShapeLayer 是在其坐标系统内绘制贝塞尔曲线的。因此,使用 CAShapeLayer 需要与 UIBezierPath 一起使用。它有一个 path 属性,而 UIBezierPath 就是对 CGPathRef 类型的封装,因此这两者要配合起来用。

CAShapeLayer与drawRect的关系

  • drawRect :属于 CoreGraphics 框架,占用 CPU ,性能消耗大,不建议重写
  • CAShapeLayer :属于 CoreAnimation 框架,通过 GPU 来渲染图形,节省性能。动画渲染直接提交给手机 GPU ,不消耗内存
    这两者各有各的用途,而不是说有了 CAShapeLayer 就不需要 drawRect。
    温馨提示:drawRect只是一个方法而已,是 UIView 的方法,重写此方法可以完成我们的绘制图形功能。

CAShapeLayer与UIBezierPath的关系

  • CAShapeLayer中shape代表形状的意思,所以需要形状才能生效
  • 贝塞尔曲线可以创建基于矢量的路径,而UIBezierPath类是对CGPathRef的封装
  • 贝塞尔曲线给CAShapeLayer提供路径,CAShapeLayer在提供的路径中进行渲染。路径会闭环,所以绘制出了Shape
  • 用于CAShapeLayer的贝塞尔曲线作为path,其path是一个首尾相接的闭环的曲线,即使该贝塞尔曲线不是一个闭环的曲线
- (void)viewDidLoad {
    [super viewDidLoad];
    _triangleView = [[UIView alloc] initWithFrame:CGRectMake(screenWidth / 4, 200, screenWidth / 2, screenHeight / 2)];
    _triangleView.backgroundColor = [UIColor orangeColor];
    [self.view addSubview:_triangleView];
    _triangleView.layer.mask = [self createMaskLayerWithView];
}

- (CALayer *)createMaskLayerWithView
{
    CGFloat viewWidth = CGRectGetWidth(_triangleView.frame);
    CGFloat viewHeight = CGRectGetHeight(_triangleView.frame);

    CGFloat rightSpace = 10.;
    CGFloat topSpace = 15.;

    // 起点
    CGPoint point1 = CGPointMake(0, 0);

    // 绘制线条
    CGPoint point2 = CGPointMake(viewWidth-rightSpace, 0);
    CGPoint point3 = CGPointMake(viewWidth-rightSpace, topSpace);
    CGPoint point4 = CGPointMake(viewWidth, topSpace);
    CGPoint point5 = CGPointMake(viewWidth-rightSpace, topSpace+10.);
    CGPoint point6 = CGPointMake(viewWidth-rightSpace, viewHeight);
    CGPoint point7 = CGPointMake(0, viewHeight);

    UIBezierPath *path = [UIBezierPath bezierPath];
    [path moveToPoint:point1];
    [path addLineToPoint:point2];
    [path addLineToPoint:point3];
    [path addLineToPoint:point4];
    [path addLineToPoint:point5];
    [path addLineToPoint:point6];
    [path addLineToPoint:point7];
    [path closePath];

    CAShapeLayer *layer = [CAShapeLayer layer];
    layer.path = path.CGPath;
    return layer;
}

CoreGraphics

这是一个绘图专用的API族,它经常被称为QuartZ或QuartZ 2D。Core Graphics是iOS上所有绘图功能的基石,包括UIKit,Core Graphics是高度集成于UIView和其他UIKit部分,Core Graphics数据结构和函数可以通过前缀CG来识别。

步骤:

  • 1.先在drawRect方法中获得上下文context;
  • 2.绘制图形(线,图形,图片等);
  • 3.设置一些修饰属性;
  • 4.渲染到上下文,完成绘图。
- (void)drawRect:(CGRect)rect
{
    CGContextRef c = UIGraphicsGetCurrentContext();

    CGContextSetRGBStrokeColor(c, 0.0, 0.0, 0.0, 1.0);    // black
    CGContextSetLineWidth(c, 1);
    CGMutablePathRef bubblePath = CGPathCreateMutable();

    // 这里的点是三角形的尖尖
    CGPathMoveToPoint(bubblePath, NULL, self.frame.origin.x / 2, self.frame.origin.y);
    // 其中的一条边的终点
    CGPathAddLineToPoint(bubblePath, NULL, 0, self.frame.size.height / 3);
    // 另一条边的终点
    CGPathAddLineToPoint(bubblePath, NULL, self.frame.size.width, self.frame.size.height / 3);

    CGPathCloseSubpath(bubblePath);
    CGContextSaveGState(c);
    CGContextAddPath(c, bubblePath);
    CGContextClip(c);

    CGContextSetFillColorWithColor(c, [[UIColor blueColor] CGColor]);
    CGContextFillRect(c, self.bounds);
    CGPathRelease(bubblePath);
}

注意事项:

  • 1.绘图需要 CGContextRef,CGContextRef即图形上下文。可以这么理解,我们绘图是需要一个载体或者说输出目标,它用来显示绘图信息,并且决定绘制的东西输出到哪个地方。可以形象的比喻context就像一个“画板”,我们得把图形绘制到这个画板上。所以,绘图必须要先有context;
  • 2.并不是说一提到绘图,就一定得重写drawRect方法,只是因为通常情况下我们一般采用在drawRect方法里获取context这种方式。
  • drawRect方法什么时候触发?当view第一次显示到屏幕上时;当调用view的setNeedsDisplay或者setNeedsDisplayInRect:方法时。

CGContextAddArcToPoint && CGPathAddArcToPoint

CGContextAddArcToPoint与CGPathAddArcToPoint这两函数是根据两切线及角度来画弧度,设置弧度CGFloat radius。

画一个四个角都是圆角的矩形,

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    [self drawArcToPoint2];
}

- (void)drawArcToPoint2
{
    //创建CGContextRef
    UIGraphicsBeginImageContext(self.view.bounds.size);
    CGContextRef gc = UIGraphicsGetCurrentContext();

    //===绘画逻辑 ===
    //创建用于转移坐标的Transform,如许我们不消遵守实际显示做坐标策画
    CGAffineTransform transform = CGAffineTransformMakeTranslation(200,200);
    //创建CGMutablePathRef
    CGMutablePathRef path = CGPathCreateMutable();
    //半径为10
    CGFloat radius = 10;

    //初始点为(10, 0),起点要从10开始,否则,在左上角会出现线条
    CGPathMoveToPoint(path, &transform, 10, 0);
    //右上角和右下角两个点,画出半个圆角
    CGPathAddArcToPoint(path, &transform,200, 0, 200, 200, radius);
    //右下角,画出别的半个圆角
    CGPathAddArcToPoint(path, &transform,200, 200, 0, 200, radius);
    //左下角
    CGPathAddArcToPoint(path, &transform,0, 200, 0,0, radius);
    //左上角
    CGPathAddArcToPoint(path, &transform,0, 0, 200, 0, radius);

    //将CGMutablePathRef添加到当前Context内
    CGContextAddPath(gc, path);
    [[UIColor grayColor] setFill];
    [[UIColor blueColor] setStroke];
    CGContextSetLineWidth(gc,2);

    //履行绘画
    CGContextDrawPath(gc,kCGPathFillStroke);
    //从Context中获取图像,并显示在界面上
    UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    UIImageView *imgView = [[UIImageView alloc] initWithImage:img];
    [self.view addSubview:imgView];
}

CAShapedLayer处理

理论上我们可以构造出任意想要的形状,但是有些形状如果你不熟悉几何知识的话是构造不出正确path的,从代码上我们可以看到我们可以通过设置CALayer的contents属性来设置显示的内容,那我们是不是可以通过设置CAShapedLayer的contents来设maskLayer呢?答案是肯定的,代码如下:

- (void)setup4
{
    CAShapeLayer *maskLayer = [CAShapeLayer layer];
    maskLayer.fillColor = [UIColor blackColor].CGColor;
    maskLayer.strokeColor = [UIColor clearColor].CGColor;
    maskLayer.frame = self.bounds;
    maskLayer.contentsCenter = CGRectMake(0.5, 0.5, 0.1, 0.1);
    maskLayer.contentsScale = [UIScreen mainScreen].scale;                 //非常关键设置自动拉伸的效果且不变形
    maskLayer.contents = (id)[UIImage imageNamed:@"gray_bubble_right@2x.png"].CGImage;

    CALayer *contentLayer = [CALayer layer];
    contentLayer.mask = _maskLayer;
    contentLayer.frame = self.bounds;
    [self.layer addSublayer:_contentLayer];
}

遗留问题

使用CGPathAddArcToPoint绘制带有箭头的弹窗菜单,如图,
弹窗菜单

在绘制四个边的圆角时,无法对应到特定的四个角的位置,导致绘制不出该效果的图片。

demo

Github :https://github.com/XibHe/DrawTriangle

参考资料

–EOF–

若无特别说明,本站文章均为原创,转载请保留链接,谢谢