iOS開發---繪圖
繪圖操作
前言:在iOS開發中,系統已經給我們提供了功能強大的控制項,可是很多並不能滿足我們的需求。這時候我們需要,自訂一些美觀的控制項。所用的知識也就是下面的繪圖。
1.基本的繪圖知識1.1圖形上下文
1.1圖形上下文(Graphics Context):是一個CGContextRef類型的資料
1.2圖形內容相關的作用
儲存繪圖資訊、繪圖狀態
決定繪製的輸出目標(繪製到什麼地方去?)
(輸出目標可以是PDF檔案、Bitmap或者顯示器的視窗上)
1.2 - (void)drawRect:(CGRect)rect作用:就是用來繪圖 什麼調用:當控制項第一次顯示的時候 rect:當前控制項的bounds1.2 繪圖的步驟
1.獲得圖形上下文:
CGContextRef ctx = UIGraphicsGetCurrentContext();
2.拼接路徑
建立一個起點
void CGContextMoveToPoint(CGContextRef c, CGFloat x, CGFloat y)
添加新的線段到某個點
void CGContextAddLineToPoint(CGContextRef c, CGFloat x, CGFloat y)
添加一個矩形
void CGContextAddRect(CGContextRef c, CGRect rect)
添加一個橢圓
void CGContextAddEllipseInRect(CGContextRef context, CGRect rect)
添加一個圓弧
void CGContextAddArc(CGContextRef c, CGFloat x, CGFloat y,
CGFloat radius, CGFloat startAngle, CGFloat endAngle, int clockwise)
3.添加路徑到上下文
Mode參數決定繪製的模式
void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode)
繪製空心路徑
void CGContextStrokePath(CGContextRef c)
繪製實心路徑
void CGContextFillPath(CGContextRef c)
提示:一般以CGContextDraw、CGContextStroke、CGContextFill開頭的函數,都是用來繪製路徑的
4.渲染到View上面
1.3 基本的繪圖執行個體
1. 繪製線條
獲得圖形上下文:
UIGraphicsGetCurrentContext()
拼接路徑:
UIBezierPath
添加路徑到上下文
CGContextAddPath(ctx, path.CGPath)
渲染到View上面
CGContextStrokePath(ctx)
- (void)drawLine{ // 一個路徑對象,可以對應多跟線 // 1.擷取跟當前view想關聯的上下文,系統自動幫我們建立的上下文 CGContextRef ctx = UIGraphicsGetCurrentContext(); // 設定內容相關的狀態,應該放在渲染之前就可以 // 建立對應顏色對象,調用set方法 [[UIColor redColor] set]; // 設定內容相關的線寬 CGContextSetLineWidth(ctx, 15); // 設定線段的串連樣式 CGContextSetLineJoin(ctx, kCGLineJoinRound); // 設定線段的端點樣式 CGContextSetLineCap(ctx, kCGLineCapRound); // 2.拼接路徑,UIBezierPath,封裝好了一套很好使用的路徑 UIBezierPath *path = [UIBezierPath bezierPath]; // 2.1 設定起點 [path moveToPoint:CGPointMake(10, 125)]; // 2.2 添加一根線到某個點 [path addLineToPoint:CGPointMake(220, 125)]; // 如果只使用一根路徑,預設下一根線的起點在上一根跟線終點 [path addLineToPoint:CGPointMake(200, 150)]; // 3.添加路徑到上下文 CGContextAddPath(ctx, path.CGPath); // 4.渲染到view上面的圖層 CGContextStrokePath(ctx);}
2.繪製曲線
一般通過貝茲路徑來繪製圖形:UIBezierPath
-(void)drawLIneQuadCurve{ // 1.擷取上下文 CGContextRef ctx = UIGraphicsGetCurrentContext(); // 2.拼接路徑 UIBezierPath *path = [UIBezierPath bezierPath]; [path moveToPoint:CGPointMake(10, 125)]; [path addQuadCurveToPoint:CGPointMake(240, 125) controlPoint:CGPointMake(125, 0)]; // 3.路徑添加到上下文 CGContextAddPath(ctx, path.CGPath); // 4.渲染上下文 //以填充的方式渲染 //CGContextFillPath(ctx); CGContextStrokePath(ctx);}
3.繪製矩形
// 繪製矩形- (void)drawRect{ CGContextRef ctx = UIGraphicsGetCurrentContext(); UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(50, 50, 100, 100) cornerRadius:50]; CGContextAddPath(ctx, path.CGPath); // 設定填充的顏色 [[UIColor redColor] setFill]; [[UIColor greenColor] setStroke]; // 填充:必須封閉路徑 // 即描邊又填充 // 如果以後只需要描邊,最好不要使用fill CGContextDrawPath(ctx, kCGPathFillStroke); // CGContextFillPath(ctx); CGContextStrokePath(ctx);}
4.繪製圓弧
- (void)drawRect:(CGRect)rect { // 畫圓弧 // center:圓心 // radius:半徑 // clockwise:當前是 yes:順時針 no:逆時針 CGPoint center = CGPointMake(125, 125); UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(125, 125) radius:100 startAngle:0 endAngle:-M_PI_2 clockwise:NO]; // 添加一根線到圓心 [path addLineToPoint:center]; // 關閉路徑:從路徑的終點到起點 // [path closePath]; // 填充,預設會關閉路徑,從路徑的終點到起點 [path fill];}
5.繪製文字
- (void)drawRect:(CGRect)rect{ //=========================繪製文字========================== NSString *name = @會長桂雛菊; // 標題文字的屬性,顏色,字型大小 NSMutableDictionary *attr = [NSMutableDictionary dictionary]; // 字型 attr[NSFontAttributeName] = [UIFont systemFontOfSize:15]; // 顏色 attr[NSForegroundColorAttributeName] = [UIColor redColor]; // 邊框顏色 attr[NSStrokeColorAttributeName] = [UIColor redColor]; // 邊框寬度 attr[NSStrokeWidthAttributeName] = @1; // 陰影 NSShadow *shadow = [[NSShadow alloc] init]; shadow.shadowOffset = CGSizeMake(3, 3); shadow.shadowColor = [UIColor yellowColor]; shadow.shadowBlurRadius = 3; attr[NSShadowAttributeName] = shadow; [name drawInRect:CGRectMake(90, 100, 100, 50) withAttributes:attr];}
6.繪製餅狀圖
在初始化的時候時候,系統通過調用drawRect方法繪圖。但是如果我們要重繪,手動調用drawRect方法是無效的。不過系統為我們準備了重繪的方法:
重繪:setNeedsDisplay
- (void)drawRect:(CGRect)rect { // Drawing code NSArray *datas = @[@25,@25,@50]; CGPoint center = CGPointMake(125, 125); CGFloat r = 100; CGFloat startA = 0; CGFloat angle = 0; CGFloat endA = 0; for (NSNumber *number in datas) { // 遍曆一個資料,繪製一根扇形 startA = endA; angle = number.intValue / 100.0 * M_PI * 2; endA = startA + angle; // 描述圓弧 UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:r startAngle:startA endAngle:endA clockwise:YES]; [path addLineToPoint:center]; [[self randomColor] set]; [path fill]; }}//當點擊View的時候,重繪- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ [self setNeedsDisplay];}//產生隨機的顏色- (UIColor *)randomColor{ CGFloat r = arc4random_uniform(256) / 255.0; CGFloat g = arc4random_uniform(256) / 255.0; CGFloat b = arc4random_uniform(256) / 255.0; return [UIColor colorWithRed:r green:g blue:b alpha:1];}
7.上下文棧
將當前的上下文copy一份,儲存到棧頂(那個棧叫做”圖形上下文棧”):
void CGContextSaveGState(CGContextRef c)
將棧頂的上下文出棧,替換掉當前的上下文:
void CGContextRestoreGState(CGContextRef c)
- (void)drawRect:(CGRect)rect { // Drawing code // 1.擷取上下文 CGContextRef ctx = UIGraphicsGetCurrentContext(); // 2.拼接路徑 UIBezierPath *path = [UIBezierPath bezierPath]; // 橫 [path moveToPoint:CGPointMake(10, 150)]; [path addLineToPoint:CGPointMake(290, 150)]; // 3.把路徑添加到上下文 CGContextAddPath(ctx, path.CGPath); //================================================ // 儲存上下文狀態 CGContextSaveGState(ctx); //================================================ // 設定內容相關的狀態 CGContextSetLineWidth(ctx, 10); [[UIColor redColor] set]; // 4.渲染上下文,查看內容相關的狀態,根據狀態去渲染 CGContextStrokePath(ctx); // 豎 path = [UIBezierPath bezierPath]; [path moveToPoint:CGPointMake(150, 10)]; [path addLineToPoint:CGPointMake(150, 290)]; // 3.把路徑添加到上下文 CGContextAddPath(ctx, path.CGPath); // ============================================== // 從上下文狀態棧裡面取出儲存的狀態,替換掉當前的狀態 CGContextRestoreGState(ctx); // ============================================= // 4.渲染上下文,查看內容相關的狀態,根據狀態去渲染 CGContextStrokePath(ctx);}
8.內容相關的平移,旋轉,縮放
利用矩陣操作,能讓繪製到上下文中的所有路徑一起發生變化
縮放
void CGContextScaleCTM(CGContextRef c, CGFloat sx, CGFloat sy)
旋轉
void CGContextRotateCTM(CGContextRef c, CGFloat angle)
平移
void CGContextTranslateCTM(CGContextRef c, CGFloat tx, CGFloat ty)
- (void)drawRect:(CGRect)rect { // 擷取上下文 CGContextRef ctx = UIGraphicsGetCurrentContext(); // 拼接路徑 UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(-100, -50, 200, 100)]; // 矩陣操作:必須要在添加路徑之前進行形變 // 平移上下文 CGContextTranslateCTM(ctx, 100, 100); // 旋轉 CGContextRotateCTM(ctx, M_PI_4); // 縮放 CGContextScaleCTM(ctx, 0.5, 0.5); // 添加路徑到上下文 CGContextAddPath(ctx, path.CGPath); // 渲染上下文 CGContextFillPath(ctx);}
9.圖片加浮水印
1.開啟一個基於位元影像的圖形上下文
void UIGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale)
size:上下文尺寸 opaque:不透明 Yes 不透明 透明 NO scale:是否縮放上下文,0表示不要縮放
2.從上下文中取得圖片(UIImage):
UIImage* UIGraphicsGetImageFromCurrentImageContext()
3.結束基於位元影像的圖形上下文:
void UIGraphicsEndImageContext()
- (void)viewDidLoad {開啟一個基於位元影像的圖形上下文void UIGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale) [super viewDidLoad]; UIImage *image = [UIImage imageNamed:@hina]; // 建立位元影像上下文 UIGraphicsBeginImageContextWithOptions(image.size, NO, 0); [image drawAtPoint:CGPointZero]; // 文字 NSString *str = @會長桂雛菊; [str drawAtPoint:CGPointMake(0, 0) withAttributes:nil]; // 根據內容相關的內容產生一張圖片 image = UIGraphicsGetImageFromCurrentImageContext(); // 關閉上下文 UIGraphicsEndImageContext(); // 用來網路中傳輸圖片 NSData *data = UIImagePNGRepresentation(image); [data writeToFile:@/Users/apple/Desktop/image.png atomically:YES];}
10.圖片裁剪
1.將當前上下所繪製的路徑裁剪出來(超出這個裁剪地區的都不能顯示):
void CGContextClip(CGContextRef c)
思路分析
先畫一個大圓,在設定裁剪地區,把圖片畫上去,超出裁剪地區的自動裁剪掉。
* 載入舊圖片,根據舊圖片,擷取上下文尺寸。
* 確定圓環寬度 borderW
* 內容相關的尺寸 = 新圖片的尺寸
* 確定新的上下文尺寸: newImageW : oldImageW + 2 * borderW newImageH : oldImageH + 2 * borderW,
* 繪製大圓:
1.擷取上下文 2.添加路徑到上下文 3.設定大圓的顏色 = 圓環的顏色 4.渲染
* 設定裁剪地區,和圖片尺寸一樣大,只不過,x,y不一樣,x=borderW,y=borderW.
* 繪製舊圖片
* 擷取新圖片
* 關閉上下文
* 抽分類,3個參數,圖片名稱,圓環寬度,圓環顏色
+ (UIImage*)imageCircleWithImage:(UIImage *)image borderWidth:(CGFloat)borderWidth borderColor:(UIColor *)borderColor{ // 設定圓環寬度 CGFloat boardW = borderWidth; CGFloat imageW = image.size.width + 2 * boardW; CGFloat imageH = image.size.height + 2 *boardW; // 只有正方形才能正切圓,選擇一個最短的尺寸,正切。 CGFloat circleW = imageW > imageH ? imageH : imageW; CGRect rect = CGRectMake(0, 0, circleW, circleW); // 2.開啟映像上下文 UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0.0); // 3。擷取當前上下文 CGContextRef ctx = UIGraphicsGetCurrentContext(); // 4.畫外圓 UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:rect]; CGContextAddPath(ctx, path.CGPath); [borderColor set]; CGContextFillPath(ctx); // 設定頭像尺寸 rect = CGRectMake(boardW, boardW, image.size.width , image.size.height); // 5.建立裁剪路徑 UIBezierPath *clipPath = [UIBezierPath bezierPathWithOvalInRect:rect]; // 6.裁剪路徑 // 根據這個路徑進行裁剪,超出路徑以外的部分就不會顯示了 [clipPath addClip]; // 7.畫頭像 [image drawInRect:rect]; // 不能直接在這返回,上下文沒有關閉,會消耗記憶體. // 8.擷取新圖片 image = UIGraphicsGetImageFromCurrentImageContext(); // 9.關閉上下文 UIGraphicsEndImageContext(); return image;}
11.截取螢幕
View之所以能顯示東西,完全是因為它內部的layer。View內部有個layer(圖層)屬性,drawRect:方法中取得的是一個Layer Graphics Context,因此,繪製的東西其實是繪製到view的layer上去了
- (void)renderInContext:(CGContextRef)ctx;
//截取螢幕+ (UIImage*)imageWithCaptureView:(UIView*)captureView;{ // 1.開啟上下文 UIGraphicsBeginImageContextWithOptions(captureView.bounds.size, NO, 0.0); // 2.擷取當前上下文 CGContextRef ctx = UIGraphicsGetCurrentContext(); // 3.把控制器圖層渲染到上下文 [captureView.layer renderInContext:ctx]; // 4.取出新圖片 UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return newImage;}