在iOS開發(fā)過程中,我們會經(jīng)常遇到畫線的功能巡社,比如線性圖膛堤。
目前iOS畫線有兩大類方法 (我所知道的)。
1晌该、基于CoreGraphics.framework
的CGContext
肥荔;
2绿渣、基于UIKit.framework
、QuartzCore.framework
的UIBezierPath
次企、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);
}
效果圖:
注意
CGContextClosePath
方法!有很多人在使用這個方法時踏志,會遇到
<Error>: CGContextClosePath: no current point.
這是因為再執(zhí)行
CGContextClosePath
方法時阀捅,當前已經(jīng)沒有可用點了。比如執(zhí)行CGContextStrokePath
针余、CGContextFillPath
饲鄙、CGContextEOFillPath
后,都會置空當前點圆雁,所以CGContextClosePath
方法一般在這些方法之前執(zhí)行忍级。
方法二、UIBezierPath
和CAShapeLayer
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];
}
效果圖:
當然你也可以借助使用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];
}
效果圖:
看上去效果是不是和UIBezierPath
一樣朴肺?
注意CAShapeLayer
的strokeStart
和strokeEnd
!這兩個參數(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];
}
效果圖:
注意
1禽拔、NSArray *array = [NSArray arrayWithObjects:v0, v0, v1, v2, v3, v3, nil];
這里為什么我多加了一個v0
和v3
刘离,因為Catmull-Rom需要四個點!為了能畫出v0
到v1
之間的線和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算法可以進行封裝狗超,這點我在接下來的文章中會有介紹。