iOS離散點畫曲線

在iOS開發(fā)過程中,我們會經(jīng)常遇到畫線的功能巡社,比如線性圖膛堤。

目前iOS畫線有兩大類方法 (我所知道的)。
1晌该、基于CoreGraphics.frameworkCGContext肥荔;
2绿渣、基于UIKit.frameworkQuartzCore.frameworkUIBezierPath次企、CAShapeLayer怯晕。

方法一、CGContext

CGContext是一個結(jié)構(gòu)體缸棵。

下面列舉與畫線相關(guān)的方法:

//繪制直線
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)
//根據(jù)指定路徑繪制
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
    //當前繪制區(qū)域(上下文)
    CGContextRef context = UIGraphicsGetCurrentContext();
    //開啟一個新路徑吧凉,放棄舊路徑
    CGContextBeginPath(context);
    //設(shè)置線條粗細
    CGContextSetLineWidth(context, 1.0);
    //設(shè)置描邊顏色
    UIColor *strokeColor = [UIColor redColor];
    CGContextSetStrokeColorWithColor(context, strokeColor.CGColor);
    //設(shè)置起始點
    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.
這是因為再執(zhí)行CGContextClosePath方法時阀捅,當前已經(jīng)沒有可用點了。比如執(zhí)行CGContextStrokePath针余、CGContextFillPath 饲鄙、CGContextEOFillPath后,都會置空當前點圆雁,所以CGContextClosePath方法一般在這些方法之前執(zhí)行忍级。

方法二、UIBezierPathCAShapeLayer

UIBezierPath繼承NSObject伪朽;CAShapeLayer繼承CALayer轴咱,CALayer繼承NSObject

下面列舉與畫線相關(guān)的方法:

//繪制直線(從上一個點到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
    //設(shè)置顏色
    [[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
    //設(shè)置顏色
    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!這兩個參數(shù)配合動畫坚洽,會有不錯的效果戈稿。

上面說了那么多,都還沒介紹繪制曲線方法讶舰。

其實上面有說明鞍盗,比如二次貝塞爾曲線、三次貝塞爾曲線绘雁、圓弧都是曲線的繪制方法橡疼。

可是這些都不是我這次要介紹的,首先貝塞爾曲線需要添加控制點庐舟,說實話欣除,這些控制點在實際項目中很不好找,其次圓弧不能滿足所有曲線類型挪略。

那怎么辦呢历帚?

答案是使用Catmull-Rom算法滔岳。
這個算法思想,大家可以在網(wǎng)上搜索挽牢。
這里我直接給出iOS下谱煤,我的處理方式。
直接上代碼:

/**
 Catmull-Rom算法
 根據(jù)四個點計算中間點
 */
- (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循環(huán)中for (int i=0; i<100; i++)野来,這里i我為什么取100為最大值恼除?因為兩個點之間的距離是100。當然這個值曼氛,可以根據(jù)實際需要豁辉,由自己來設(shè)定。

好了舀患,到這里基本就要結(jié)束了徽级。

離散點畫曲線,我采用的是Catmull-Rom算法构舟,在離散點之間插入中間點灰追。

基礎(chǔ)是兩點之間畫直線堵幽,關(guān)于Catmull-Rom算法可以進行封裝狗超,這點我在接下來的文章中會有介紹。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末朴下,一起剝皮案震驚了整個濱河市努咐,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌殴胧,老刑警劉巖渗稍,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異团滥,居然都是意外死亡竿屹,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進店門灸姊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拱燃,“玉大人,你說我怎么就攤上這事力惯⊥胗” “怎么了召嘶?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長哮缺。 經(jīng)常有香客問我弄跌,道長,這世上最難降的妖魔是什么尝苇? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任铛只,我火速辦了婚禮,結(jié)果婚禮上糠溜,老公的妹妹穿的比我還像新娘格仲。我一直安慰自己,他們只是感情好诵冒,可當我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布凯肋。 她就那樣靜靜地躺著,像睡著了一般汽馋。 火紅的嫁衣襯著肌膚如雪侮东。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天豹芯,我揣著相機與錄音悄雅,去河邊找鬼。 笑死铁蹈,一個胖子當著我的面吹牛宽闲,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播握牧,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼容诬,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了沿腰?” 一聲冷哼從身側(cè)響起览徒,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎颂龙,沒想到半個月后习蓬,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡措嵌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年躲叼,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片企巢。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡枫慷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情流礁,我是刑警寧澤涕俗,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站神帅,受9級特大地震影響再姑,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜找御,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一元镀、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧霎桅,春花似錦栖疑、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至揭糕,卻和暖如春萝快,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背著角。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工揪漩, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人吏口。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓奄容,卻偏偏與公主長得像,于是被迫代替她去往敵國和親产徊。 傳聞我的和親對象是個殘疾皇子昂勒,可洞房花燭夜當晚...
    茶點故事閱讀 44,914評論 2 355

推薦閱讀更多精彩內(nèi)容