iOS之CAShapeLayer和貝塞爾曲線的使用
最近在開發一個新專案,專案裡面需要繪圖的地方比較多,所以就花點時間把iOS開發中經常使用的CAShapeLayer相關的知識進行梳理總結。初步效果如下圖

效果圖
1、CAShapeLayer簡介
1.1 顧名思義CAShapeLayer繼承自CALayer,所以CALayer的所有屬性方法CAShapeLayer都可以使用
1.2 CAShapeLayer需要與貝塞爾曲線配合使用才有意義
1.3 使用CAShapeLayer與貝塞爾曲線可以實現不在view的drawRect方法中畫出有一些想要的圖形
1.4 CAShapeLayer屬於CoreAnimation框架,其動畫渲染直接提交到手機的GPU當中,相較於view的drawRect方法使用CPU渲染而言,其效率極高,能大大優化記憶體使用情況。
2、CAShapeLayer與UIBezierPath的關係
2.1 CAShapeLayer中shape代表形狀的意思,所以需要形狀才能生效
2.2 貝塞爾曲線可以建立基於向量的路徑,而UIBezierPath類是對CGPathRef的封裝
2.3 貝塞爾曲線給CAShapeLayer提供路徑,CAShapeLayer在提供的路徑中進行渲染。路徑會閉環,所以繪製出了Shape
2.4 用於CAShapeLayer的貝塞爾曲線作為path,其path是一個首尾相接的閉環的曲線,即使該貝塞爾曲線不是一個閉環的曲線
3、專案簡介
本專案主要是顯示盾構機的數量,掘進狀態,故障資訊,報警資訊等。我負責的是顯示盾構機的狀態,包括掘進狀態以及模擬盾首和盾尾在真實環境下的軌跡偏差, 包括水平偏差和垂直偏差。
首先是繪製弧形的刻度尺,話不多少程式碼很詳細:
-(void)setCompassView{ CGFloat perAngle = M_PI/(180); self.frame = self.view.frame; //畫圓弧,每隔1°畫一個弧線,總共60條 for (int i = 0; i <= 60; i++) { //起始角度 CGFloat startAngle = ((M_PI_2 *3 + M_PI / 18 + M_PI/180/2)+perAngle*i); CGFloat endAngle = startAngle+perAngle/2; //畫圓弧 UIBezierPath *bezPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(self.frame.size.width/2, self.frame.size.height/2) radius:(self.frame.size.width/2 - 50) startAngle:startAngle endAngle:endAngle clockwise:YES]; CAShapeLayer *shapeLayer = [CAShapeLayer layer]; //每隔15°畫一個白條刻度 if (i%15 == 0) { shapeLayer.strokeColor = [[UIColor whiteColor] CGColor]; shapeLayer.lineWidth = 20; }else{ shapeLayer.strokeColor = [[UIColor grayColor] CGColor]; shapeLayer.lineWidth = 10; } shapeLayer.path = bezPath.CGPath; shapeLayer.fillColor = [UIColor clearColor].CGColor; [self.view.layer addSublayer:shapeLayer]; //新增刻度說明 if (i % 15 == 0){ NSString *tickText; if (i ==0) { tickText = @"-4"; }else if (i==15){ tickText = @"-2"; }else if(i==30){ tickText = @"0"; } else if (i==45){ tickText = @"2"; }else if (i==60){ tickText = @"4"; } CGFloat textAngel = -startAngle * (180/M_PI);//記得在這裡換算成角度 //根據提供的圓點、角度和半徑計算圓周上一點的座標 CGPoint point = [self calcCircleCoordinateWithCenter:CGPointMake(self.frame.size.width / 2, self.frame.size.height / 2) andWithAngle:textAngel andWithRadius:(self.frame.size.width/2 - 26)]; UILabel *label = [[UILabel alloc]initWithFrame:CGRectMake(point.x, point.y, 30, 15)]; label.center = point; label.text = tickText; label.textColor = [UIColor grayColor]; label.font = [UIFont systemFontOfSize:15]; label.textAlignment = NSTextAlignmentCenter; [self.view addSubview:label]; } } }
接下來的就是繪製刻度尺上的指標,要求是指標可以根據提供的角度進行移動。
-(void)setPoint:(CGPoint)pointT pointB:(CGPoint)pointB lineColor:(UIColor *)color{ //設定刻度盤上的綠指標和紅指標 //perAngle >0,下滑 CAShapeLayer *shapeLayer = [CAShapeLayer layer]; UIBezierPath *linePath = [UIBezierPath bezierPath]; [linePath moveToPoint:pointT]; [linePath addLineToPoint:pointB]; shapeLayer.path = linePath.CGPath; shapeLayer.backgroundColor = [UIColor clearColor].CGColor; shapeLayer.strokeColor = color.CGColor; shapeLayer.lineWidth = 2; shapeLayer.fillColor = [UIColor clearColor].CGColor; [self.view.layer addSublayer:shapeLayer]; }
由於指標我採用的是使用UIBezierPath繪製直線,所以重點是計算出來直線的起點和終點的座標,根據數學知識可知,求圓上點的座標需要已知的條件:圓心、半徑、角度

圖片來源網路
在iOS開發中我們這樣做
-(CGPoint) calcCircleCoordinateWithCenter:(CGPoint) centerandWithAngle : (CGFloat) angle andWithRadius: (CGFloat) radius{ CGFloat x2 = radius*cosf(angle*M_PI/180); CGFloat y2 = radius*sinf(angle*M_PI/180); return CGPointMake(center.x+x2, center.y-y2); }
好了,有了點的座標那麼我們就可以繪製指標了:
-(void)setPoint:(CGPoint)pointT pointB:(CGPoint)pointB lineColor:(UIColor *)color{ //設定刻度盤上的綠指標和紅指標 //perAngle >0,下滑 CAShapeLayer *shapeLayer = [CAShapeLayer layer]; UIBezierPath *linePath = [UIBezierPath bezierPath]; [linePath moveToPoint:pointT]; [linePath addLineToPoint:pointB]; shapeLayer.path = linePath.CGPath; shapeLayer.backgroundColor = [UIColor clearColor].CGColor; shapeLayer.strokeColor = color.CGColor; shapeLayer.lineWidth = 2; shapeLayer.fillColor = [UIColor clearColor].CGColor; [self.view.layer addSublayer:shapeLayer]; }
這個是繪製兩個圓的程式碼實現:
CGFloat sceenW = [UIScreen mainScreen].bounds.size.width; CGFloat sceenH = [UIScreen mainScreen].bounds.size.height; CAShapeLayer *layer = [CAShapeLayer layer]; layer.frame = self.view.bounds; //設定背景色 layer.backgroundColor = [UIColor clearColor].CGColor; //設定描邊色 layer.strokeColor = [UIColor orangeColor].CGColor; //設定填充色 layer.fillColor = [UIColor clearColor].CGColor; //圓 UIBezierPath *outCircle = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(sceenW/2 -120, sceenH/2 - 120, 240, 240)]; UIBezierPath *inCircle = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(sceenW/2 -100, sceenH/2 - 100, 200, 200)]; //直線 UIBezierPath *transverseLine = [UIBezierPath bezierPath]; [transverseLine moveToPoint:CGPointMake(sceenW/2 - 120, sceenH/2)]; [transverseLine addLineToPoint:CGPointMake(sceenW/2 + 120, sceenH/2)]; UIBezierPath *verticalLine = [UIBezierPath bezierPath]; [transverseLine moveToPoint:CGPointMake(sceenW/2, sceenH/2 -120)]; [transverseLine addLineToPoint:CGPointMake(sceenW/2, sceenH/2 + 120)]; [outCircle appendPath:inCircle]; [outCircle appendPath:transverseLine]; [outCircle appendPath:verticalLine]; layer.path = outCircle.CGPath; [self.view.layer addSublayer:layer];
好了,領導交代的任務算是基本完成,這個也只是簡單的圖形繪製,但是使用CAShapeLayer和貝塞爾曲線可以繪製你想要的任意圖形,比如自定義的圓形進度條等,並且還可以使用動畫,這樣可以讓你的app看起來更酷炫。這個就先總結到這裡吧,以後還會進行更深入的總結,估計這個可以形成一個系列專題來講了。