iOS离散点画曲线

在iOS开发过程中,我们会经常遇到画线的功能,比如线性图。

目前iOS画线有两大类方法 (我所知道的)。
1、基于CoreGraphics.frameworkCGContext
2、基于UIKit.frameworkQuartzCore.frameworkUIBezierPathCAShapeLayer

方法一、CGContext

CGContext是一个结构体。

下面列举与画线相关的方法:

//绘制直线
CGContextAddLineToPoint(CGContextRef cg_nullable c, CGFloat x, CGFloat y)
//绘制三次贝塞尔曲线
CGContextAddCurveToPoint(CGContextRef cg_nullable c, CGFloat cp1x, CGFloat cp1y, CGFloat cp2x, CGFloat cp2y, CGFloat x, CGFloat y)
//绘制二次贝塞尔曲线
CGContextAddQuadCurveToPoint(CGContextRef cg_nullable c, CGFloat cpx, CGFloat cpy, CGFloat x, CGFloat y)
//绘制矩形
CGContextAddRect(CGContextRef cg_nullable c, CGRect rect)
//绘制多个矩形
CGContextAddRects(CGContextRef cg_nullable c, const CGRect * __nullable rects, size_t count)
//绘制多个点连接的直线
CGContextAddLines(CGContextRef cg_nullable c, const CGPoint * __nullable points, size_t count)
//绘制椭圆
CGContextAddEllipseInRect(CGContextRef cg_nullable c, CGRect rect)
//绘制弧或圆
CGContextAddArc(CGContextRef cg_nullable c, CGFloat x, CGFloat y, CGFloat radius, CGFloat startAngle, CGFloat endAngle, int clockwise)
//绘制两点之间指定半径的弧(如果指定的位置、大小不合适,会绘制不出来)
CGContextAddArcToPoint(CGContextRef cg_nullable c, CGFloat x1, CGFloat y1, CGFloat x2, CGFloat y2, CGFloat radius)
//根据指定路径绘制
CGContextAddPath(CGContextRef cg_nullable c, CGPathRef cg_nullable path)

代码示例:

// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
    // Drawing code
    //当前绘制区域(上下文)
    CGContextRef context = UIGraphicsGetCurrentContext();
    //开启一个新路径,放弃旧路径
    CGContextBeginPath(context);
    //设置线条粗细
    CGContextSetLineWidth(context, 1.0);
    //设置描边颜色
    UIColor *strokeColor = [UIColor redColor];
    CGContextSetStrokeColorWithColor(context, strokeColor.CGColor);
    //设置起始点
    CGContextMoveToPoint(context, 0.0, 100.0);
    CGContextAddLineToPoint(context, 100.0, 60.0);
    CGContextAddLineToPoint(context, 200.0, 60.0);
    CGContextAddLineToPoint(context, 300.0, 100.0);
    //路径描边
    CGContextStrokePath(context);
}

效果图:

CGContext画线.png

注意CGContextClosePath方法!
有很多人在使用这个方法时,会遇到<Error>: CGContextClosePath: no current point.
这是因为再执行CGContextClosePath方法时,当前已经没有可用点了。比如执行CGContextStrokePathCGContextFillPathCGContextEOFillPath后,都会置空当前点,所以CGContextClosePath方法一般在这些方法之前执行。

方法二、UIBezierPathCAShapeLayer

UIBezierPath继承NSObjectCAShapeLayer继承CALayerCALayer继承NSObject

下面列举与画线相关的方法:

//绘制直线(从上一个点到point)
- (void)addLineToPoint:(CGPoint)point;
//绘制三次贝塞尔曲线(从上一个点到endPoint)
- (void)addCurveToPoint:(CGPoint)endPoint controlPoint1:(CGPoint)controlPoint1 controlPoint2:(CGPoint)controlPoint2;
//绘制二次贝塞尔曲线(从上一个点到endPoint)
- (void)addQuadCurveToPoint:(CGPoint)endPoint controlPoint:(CGPoint)controlPoint;
//绘制弧或圆
- (void)addArcWithCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise

代码示例:

// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
    // Drawing code
    //设置颜色
    [[UIColor orangeColor] set];
    
    UIBezierPath *path = [UIBezierPath bezierPath];
    path.lineWidth = 1.0;
    path.lineCapStyle = kCGLineCapRound;
    path.lineJoinStyle = kCGLineCapRound;
    
    CGPoint p0 = CGPointMake(0.0, 100.0);
    CGPoint p1 = CGPointMake(100.0, 120.0);
    CGPoint p2 = CGPointMake(200.0, 120.0);
    CGPoint p3 = CGPointMake(300.0, 100.0);
    
    [path moveToPoint:p0];
    [path addLineToPoint:p1];
    [path addLineToPoint:p2];
    [path addLineToPoint:p3];
    
    [path stroke];
}

效果图:

UIBezierPath画线.png

当然你也可以借助使用CAShapeLayer,示例如下:

// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
    
    // Drawing code
    //设置颜色
    UIBezierPath *path = [UIBezierPath bezierPath];
    
    CGPoint p0 = CGPointMake(0.0, 100.0);
    CGPoint p1 = CGPointMake(100.0, 120.0);
    CGPoint p2 = CGPointMake(200.0, 120.0);
    CGPoint p3 = CGPointMake(300.0, 100.0);
    
    //
    [path moveToPoint:p0];
    [path addLineToPoint:p1];
    [path addLineToPoint:p2];
    [path addLineToPoint:p3];
    
    //
    CAShapeLayer *shapeLayer = [CAShapeLayer layer];
    shapeLayer.frame = CGRectMake(0.0, 0.0, rect.size.width, rect.size.height);
    shapeLayer.lineWidth = 1.0;
    shapeLayer.lineCap = @"round";
    shapeLayer.strokeColor = [[UIColor redColor] CGColor];
    shapeLayer.fillColor = [[UIColor clearColor] CGColor];
    shapeLayer.path = [path CGPath];
    shapeLayer.strokeStart = 0.0;
    shapeLayer.strokeEnd = 1.0;
    [self.layer addSublayer:shapeLayer];
}

效果图:


CAShapeLayer.png

看上去效果是不是和UIBezierPath一样?

注意CAShapeLayerstrokeStartstrokeEnd!这两个参数配合动画,会有不错的效果。

上面说了那么多,都还没介绍绘制曲线方法。

其实上面有说明,比如二次贝塞尔曲线、三次贝塞尔曲线、圆弧都是曲线的绘制方法。

可是这些都不是我这次要介绍的,首先贝塞尔曲线需要添加控制点,说实话,这些控制点在实际项目中很不好找,其次圆弧不能满足所有曲线类型。

那怎么办呢?

答案是使用Catmull-Rom算法
这个算法思想,大家可以在网上搜索。
这里我直接给出iOS下,我的处理方式。
直接上代码:

/**
 Catmull-Rom算法
 根据四个点计算中间点
 */
- (CGPoint)getPoint:(CGFloat)t p0:(CGPoint)p0 p1:(CGPoint)p1 p2:(CGPoint)p2 p3:(CGPoint)p3 {
    CGFloat t2 = t*t;
    CGFloat t3 = t2*t;
    
    CGFloat f0 = -0.5*t3 + t2 - 0.5*t;
    CGFloat f1 = 1.5*t3 - 2.5*t2 + 1.0;
    CGFloat f2 = -1.5*t3 + 2.0*t2 + 0.5*t;
    CGFloat f3 = 0.5*t3 - 0.5*t2;
    
    CGFloat x = p0.x*f0 + p1.x*f1 + p2.x*f2 +p3.x*f3;
    CGFloat y = p0.y*f0 + p1.y*f1 + p2.y*f2 +p3.y*f3;
    
    return CGPointMake(x, y);
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
    //
    UIBezierPath *path = [UIBezierPath bezierPath];
    //
    CGPoint p0 = CGPointMake(0.0, 100.0);
    CGPoint p1 = CGPointMake(100.0, 120.0);
    CGPoint p2 = CGPointMake(200.0, 120.0);
    CGPoint p3 = CGPointMake(300.0, 100.0);
    //
    NSValue *v0 = [NSValue valueWithCGPoint:p0];
    NSValue *v1 = [NSValue valueWithCGPoint:p1];
    NSValue *v2 = [NSValue valueWithCGPoint:p2];
    NSValue *v3 = [NSValue valueWithCGPoint:p3];
    //
    NSArray *array = [NSArray arrayWithObjects:v0, v0, v1, v2, v3, v3, nil];
    //
    [path moveToPoint:p0];
    //
    for (NSInteger i=0; i<array.count - 3; i++) {
        CGPoint t0 = [array[i+0] CGPointValue];
        CGPoint t1 = [array[i+1] CGPointValue];
        CGPoint t2 = [array[i+2] CGPointValue];
        CGPoint t3 = [array[i+3] CGPointValue];
        //
        for (int i=0; i<100; i++) {
            CGFloat t = i/100.0;
            CGPoint point = [self getPoint:t p0:t0 p1:t1 p2:t2 p3:t3];
            [path addLineToPoint:point];
        }
    }
    //
    [path addLineToPoint:p3];
     //
     CAShapeLayer *shapeLayer = [CAShapeLayer layer];
     shapeLayer.frame = CGRectMake(0.0, 0.0, rect.size.width, rect.size.height);
     shapeLayer.lineWidth = 1.0;
     shapeLayer.lineCap = @"round";
     shapeLayer.strokeColor = [[UIColor redColor] CGColor];
     shapeLayer.fillColor = [[UIColor clearColor] CGColor];
     shapeLayer.path = [path CGPath];
     shapeLayer.strokeStart = 0.0;
     shapeLayer.strokeEnd = 1.0;
     [self.layer addSublayer:shapeLayer];
}

效果图:

Catmull-Rom曲线.png

注意
1、NSArray *array = [NSArray arrayWithObjects:v0, v0, v1, v2, v3, v3, nil];这里为什么我多加了一个v0v3,因为Catmull-Rom需要四个点!为了能画出v0v1之间的线和v2直接的线v3,所以我加了这两个点。

2、在第二层for循环中for (int i=0; i<100; i++),这里i我为什么取100为最大值?因为两个点之间的距离是100。当然这个值,可以根据实际需要,由自己来设定。

好了,到这里基本就要结束了。

离散点画曲线,我采用的是Catmull-Rom算法,在离散点之间插入中间点。

基础是两点之间画直线,关于Catmull-Rom算法可以进行封装,这点我在接下来的文章中会有介绍。

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

推荐阅读更多精彩内容