Quartz2D 簡介及用途
Quartz 2D 是一個二維繪圖引擎,同時支持iOS和Mac系統,Quartz2D的API是純C語言的來自于Core Graphics框架
Quartz 2D 能完成的工作:
1 繪制圖層:線條,三角形,矩形,圓,弧,扇形等
2 繪制文字
3 繪制圖片
4 讀取生成PDF
5 截圖,裁剪圖片
6 自定義UI控件Quartz 2D實力
1 裁剪圖片
2 涂鴉、畫板
3 手勢解鎖
4 折線圖、餅狀圖、柱狀圖通常在我們面對比較復雜,個性化的UI需求時,通常利用Quartz 2D通過自定義View的方法繪制出我們需要的UI
圖形上下文
如何利用Quartz 2D技術自定義繪制View
1 首先,得有圖形上下文,因為它能保存繪圖信息,并且決定著繪制到什么地方去
2 其次,那個圖形上下文必須跟view相關聯,才能將內容繪制到view上面-
自定義View的步驟
1 新建一個類,繼承自UIView
2 實現- (void)drawRect:(CGRect)rect方法,然后在這個方法中- 取得跟當前view相關聯的圖形上下文
- 繪制相應的圖形內容
- 利用圖形上下文將繪制的所有內容渲染顯示到view上面
drawRect:方法
- 只有在在drawRect:方法中才能取得跟view相關聯的圖形上下文
- 當view第一次顯示到屏幕上時(被加到UIWindow上顯示出來)調用
- 手動調用view的setNeedsDisplay或者setNeedsDisplayInRect:時
- 注意手動 [self drawRect] 是無效的寫法
繪圖方法介紹
- 畫圖步驟
1 獲取上下文
2 創建路徑(描述路徑)
3 把路徑添加到上下文
4 渲染上下文 - 畫直線(方法一)
-(void)drawRect:(CGRect)rect{
//1.獲取圖形上下文
//目前我們所用的上下文都是以UIGraphics
//CGContextRef Ref :引用 CG 目前使用到的類型和函數一般都是CG開頭
CGContextRef ctx = UIGraphicsGetCurrentContext();
//2.描述路徑
//創建路徑
CGMutablePathRef path = CGPathCreateMutable();
//設置起點
//path 給那個路徑設置起點
CGPathMoveToPoint(path, NULL, 50, 50);
//添加一根線到某個點
CGPathAddLineToPoint(path, NULL, 200, 200);
//3.把路徑添加到上下文
CGContextAddPath(ctx, path);
//4.渲染上下文
CGContextStrokePath(ctx);
}
- 畫直線(方法二)
-(void)drawRect:(CGRect)rect{
//獲取上下文 自動創建路徑
CGContextRef ctx = UIGraphicsGetCurrentContext();
//描述路徑
//設置起點
CGContextMoveToPoint(ctx, 50, 50);
CGContextAddLineToPoint(ctx, 200, 200);
//渲染上下文
CGContextStrokePath(ctx);
}
- 畫直線(方法三)
-(void)drawRect:(CGRect)rect{
//UIKIT 封裝的繪圖功能
//貝斯曲線
//創建路徑
UIBezierPath * path = [UIBezierPath bezierPath];
//設置起點
[path moveToPoint:CGPointMake(50, 50)];
//添加一個線到某個點
[path addLineToPoint:CGPointMake(100, 100)];
//繪制路徑
[path stroke];
}
- 設直線的狀態
原生設置線的狀態
-(void)drawRect:(CGRect)rect{
//畫兩根線
//獲取上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
//描述路徑
//設置起點
CGContextMoveToPoint(ctx, 50, 100);
CGContextAddLineToPoint(ctx, 100, 100);
//默認下一個線的起點就是上一根線的重點 不用重新設置
CGContextAddLineToPoint(ctx, 100, 150);
//設置繪圖狀態 在渲染之前
//color
[[UIColor greenColor] setStroke];
//線寬
CGContextSetLineWidth(ctx, 10);
//鏈接樣式
/**
*kCGLineJoinMiter,切角
*kCGLineJoinRound,圓角
*kCGLineJoinBevel
*/
CGContextSetLineJoin(ctx, kCGLineJoinRound);
// kCGLineCapButt,
// kCGLineCapRound,
// kCGLineCapSquare
//設置頂角樣式
CGContextSetLineCap(ctx, kCGLineCapRound);
//渲染上下文
CGContextStrokePath(ctx);
}
- 貝斯曲線(貝瑟爾曲線)
//貝斯 畫線 設置線的狀態
-(void)drawRect:(CGRect)rect{
//貝斯曲線設置圖形上下文狀態
//為了方便管理狀態 最好一個線對應一個路徑
UIBezierPath * path = [UIBezierPath bezierPath];
//設置起點
[path moveToPoint:CGPointMake(50, 100)];
//設置終點
[path addLineToPoint:CGPointMake(100, 100)];
//線寬
path.lineWidth = 10;
//繪制
[path stroke];
UIBezierPath * path1 = [UIBezierPath bezierPath];
[path1 moveToPoint:CGPointMake(100, 100)];
[path1 addLineToPoint:CGPointMake(100, 150)];
path1.lineWidth = 5;
[path1 stroke];
}
- 畫曲線
-(void)drawRect:(CGRect)rect{
//繪制曲線
//獲取上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
//描述路徑
//設置起點
CGContextMoveToPoint(ctx, 50, 100);
//cpx cpy 控制點的橫縱坐標和終點坐標
CGContextAddQuadCurveToPoint(ctx, 75, 50, 100, 100);
CGContextStrokePath(ctx);
}
-
畫圖形
- 圓角矩形
//圓角矩形
UIBezierPath * path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(10, 10, 100, 100) cornerRadius:50];
//[path stroke];
//填充(必須是一個封閉路徑)
[path fill];
- 橢圓
//橢圓
UIBezierPath * path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(50, 50, 50, 100)];
//填充(必須是一個封閉路徑)
[path fill];
- 畫一個圓弧
//畫一個圓弧
// Center 圓心
// startAngle 起點
// 半徑radius
// endAngle 弧度
// clockwise 順時針 逆時針
UIBezierPath * path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(100, 100) radius:50 startAngle:0 endAngle:M_PI clockwise:YES];
[path stroke];
- 畫扇形
//畫扇形
UIBezierPath * path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(100, 100) radius:50 startAngle:0 endAngle:M_PI_2 clockwise:YES];
//添加一根線到圓心
[path addLineToPoint:CGPointMake(100, 100)];
//關閉路徑
// [path closePath];
//渲染
// [path stroke];
//默認關閉路徑
[path fill];
- 畫餅狀圖
//畫餅狀圖
- (void)drawRect:(CGRect)rect {
NSArray * arr = @[@(25),@(25),@(50)];
NSArray * colorArr = @[
[UIColor redColor],
[UIColor purpleColor],
[UIColor greenColor]
];
CGFloat radius = rect.size.width*0.5;
CGPoint center = CGPointMake(radius, radius);
CGFloat startAngle = 0;
CGFloat angle = 0;
CGFloat endAngle = 0;
for (int i = 0; i <arr.count; i++) {
startAngle = endAngle;
angle = [arr[i] doubleValue]/100.0 * M_PI * 2;
endAngle = startAngle + angle;
UIBezierPath * path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES];
[path addLineToPoint:center];
UIColor * color = colorArr[i];
[color set];
[path fill];
}
}
- 畫柱狀圖
- (void)drawRect:(CGRect)rect {
NSArray * arr = @[@(25),@(25),@(50)];
NSArray * colorArr = @[
[UIColor redColor],
[UIColor purpleColor],
[UIColor greenColor]
];
CGFloat x = 0;
CGFloat y = 0;
CGFloat w = 0;
CGFloat h = 0;
for (int i = 0 ; i<arr.count; i++) {
w = rect.size.width/ (2* arr.count-1);
x = 2 * w * i;
h = [arr[i] doubleValue]/100.0 *rect.size.height;
y = rect.size.height - h;
UIBezierPath * path = [UIBezierPath bezierPathWithRect:CGRectMake(x, y, w, h)];
UIColor * color = colorArr[i];
[color set];
[path fill];
}
}
- 繪制文字
//繪制文字
-(void)drawRect:(CGRect)rect{
NSString * text = @"嘿嘿嘿嘿嘿嘿嘿";
NSMutableDictionary * textDict = [NSMutableDictionary dictionary];
//設置字體顏色
textDict[NSForegroundColorAttributeName] = [UIColor redColor];
//設置字體大小
textDict[NSFontAttributeName] = [UIFont systemFontOfSize:30];
//設置字體空心顏色和寬度
textDict[NSStrokeWidthAttributeName] = @(3);
textDict[NSStrokeColorAttributeName] = [UIColor yellowColor];
//創建shadow
NSShadow * shadow = [[NSShadow alloc]init];
shadow.shadowColor = [UIColor greenColor];
shadow.shadowOffset = CGSizeMake(4, 4);
textDict[NSShadowAttributeName] = shadow;
//繪制文字 不會換行
//[text drawAtPoint:CGPointZero withAttributes:textDict];
//可以換行
[text drawInRect:self.bounds withAttributes:textDict];
}
- 繪制圖片
- (void)drawRect:(CGRect)rect {
// 超出裁剪區域的內容全部裁剪掉
// 注意:裁剪必須放在繪制之前
UIRectClip(CGRectMake(0, 0, 50, 50));
UIImage *image = [UIImage imageNamed:@"001"];
//繪圖
// 默認繪制的內容尺寸跟圖片尺寸一樣大
// [image drawAtPoint:CGPointZero];
//圖片在rect內展示
// [image drawInRect:rect];
// 圖片平鋪
[image drawAsPatternInRect:rect];
}
- 矩陣操作
-(void)drawRect:(CGRect)rect{
//獲得上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
//設置路徑
UIBezierPath * path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, rect.size.width, rect.size.height/2)];
[[UIColor redColor] set];
//上下文的矩陣操作
//矩陣操作必須在添加路徑之前
//向下平移
CGContextTranslateCTM(ctx, 50, 50);
//縮放
CGContextScaleCTM(ctx, 0.5, 0.5);
//旋轉
CGContextRotateCTM(ctx, M_PI_4);
//把路徑添加到上下文
CGContextAddPath(ctx, path.CGPath);
//渲染上下文
CGContextFillPath(ctx);
}
位圖上下文
- 圖片水印
-
有時候防止自己的圖片被盜圖,經常在圖片右下角加上自己的水印
水印.jpeg
-
//創建一個imageview
UIImageView * imageView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 300, 558)];
imageView.center = self.view.center;
[self.view addSubview:imageView];
UIImage * image = [UIImage imageNamed:@"小黃人"];
//圖片加水印
//繪制圖片到新的圖片上 需要用到位圖上下文
//位圖上下文回去方式跟layer上下文不一樣,位圖上下文需要我們手動創建
//開啟一個位圖上下文,注意位圖上下文跟view無關,所以不需要在drawRect方法中實現
//size:位圖上下文的尺寸(新圖片的尺寸)
//opaque:不透明度 YES不透明,NO透明,通常都是透明的上下文
//scale通常不需要縮放上下文,取值為0 表示不縮放
UIGraphicsBeginImageContextWithOptions(image.size, NO, 0);
//1繪制原生圖片
[image drawAtPoint:CGPointZero];
//2給原生圖片添加文字水印
NSString * str = @"黑黑";
//創建字典屬性
NSMutableDictionary * textDic = [NSMutableDictionary dictionary];
textDic[NSForegroundColorAttributeName] = [UIColor purpleColor];
textDic[NSFontAttributeName] = [UIFont boldSystemFontOfSize:20];
[str drawAtPoint:CGPointMake(200, 528) withAttributes:textDic];
//3生成一張圖片 (從上下文中獲取)
UIImage * imageWater = UIGraphicsGetImageFromCurrentImageContext();
//關閉上下文
UIGraphicsEndImageContext();
imageView.image = imageWater;
- 圖片裁剪
-
APP中常見的圓形頭像,以及,頭像上的一層圓形的圈圈
圖片裁剪.jpeg
-
//創建一個imageview
UIImageView * imageView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 200, 200)];
imageView.center = self.view.center;
[self.view addSubview:imageView];
//加載圖片
UIImage * image = [UIImage imageNamed:@"阿貍頭像"];
//圖片寬高
CGFloat imageWH = image.size.width;
//設置圓環的寬度
CGFloat border = 1;
//圓形的寬高
CGFloat ovalWH = imageWH + 2 * border;
//1開啟個上下文
UIGraphicsBeginImageContextWithOptions(CGSizeMake(ovalWH, ovalWH), NO, 0);
//2畫大圓
UIBezierPath * path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, ovalWH, ovalWH)];
[[UIColor redColor] set];
[path fill];
//3設置裁剪區域(小圓)
UIBezierPath * clipPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(border, border, imageWH, imageWH)];
//進行裁剪
[clipPath addClip];
//4繪制圖片
[image drawAtPoint:CGPointMake(border, border)];
//5獲取圖片
UIImage * clipImage = UIGraphicsGetImageFromCurrentImageContext();
//6關閉上下文
UIGraphicsEndImageContext();
imageView.image = clipImage;
- 屏幕截屏功能
- 截取的是一個View的layer層 并不是一個圖片,要與圖片截取區分
//開啟一個位圖上下文
UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, NO, 0);
//拿到上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
//把控件上的圖層渲染到上下文
[self.view.layer renderInContext:ctx];
//不能用下面這個方法 layer 只能渲染
// [self.view.layer drawInContext:ctx];
//生成一張圖片
UIImage * image = UIGraphicsGetImageFromCurrentImageContext();
- 圖片截取
滑動手指出現矩形截圖框
- 截圖時.jpeg
松開手指截圖成功
- 截圖后.jpeg
截取的是imageView的layer層與上面的方法區分
在imageView上添加pan手勢
觸發方法
-(void)pan:(UIPanGestureRecognizer *)pan{
CGPoint endP = CGPointZero;
if(pan.state == UIGestureRecognizerStateBegan){
//獲取手指最開始的觸摸點
CGPoint startP = [pan locationInView:self.view];
self.startP = startP;
}else if (pan.state == UIGestureRecognizerStateChanged){
endP = [pan locationInView:self.view];
CGFloat w = endP.x -self.startP.x;
CGFloat h = endP.y - self.startP.y;
//獲取截圖
CGRect clipRect = CGRectMake(self.startP.x, self.startP.y, w, h);
//創建view 黑色半透明
self.clipView.frame = clipRect;
}else if (pan.state == UIGestureRecognizerStateEnded){
//開啟一個原圖一樣大的位圖上下文
UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, NO, 0);
//設置裁剪區域
UIBezierPath * clipPath = [UIBezierPath bezierPathWithRect:self.clipView.frame];
//進行裁剪
[clipPath addClip];
//獲取上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
//繪制圖片
[self.imageView.layer renderInContext:ctx];
//從上下文獲取裁剪區域
UIImage * image = UIGraphicsGetImageFromCurrentImageContext();
self.imageView.image = image;
// self.imageView.backgroundColor = [UIColor redColor];
UIGraphicsEndImageContext();
//截取的view設置為nil
[_clipView removeFromSuperview];
_clipView = nil;
}
}
- 圖片擦除
- 兩張類似圖片重疊防止,一張完整的圖片在上,另一張被擦除一部分的圖片在下,通過位圖上下文擦除上面的圖片漏出下面的完整的圖片造成假象。
- 擦除前(害羞的美女)
- 擦除前.jpeg
- 擦除后(想不想繼續扒光她)
- 擦除后.jpeg
- 在上面的圖片添加一個pan手勢,實現pan的觸發方法
-(void)pan:(UIPanGestureRecognizer *)pan{
CGPoint curP = [pan locationInView:self.view];
CGFloat wh = 50;
CGFloat x = curP.x - 0.5 * wh;
CGFloat y = curP.y - 0.5 * wh;
CGRect rect = CGRectMake(x, y, wh, wh);
//開啟上下文
UIGraphicsBeginImageContextWithOptions(self.view.frame.size, NO, 0);
//獲取上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
//把上面那張控件的layer渲染上去
[self.imageA.layer renderInContext:ctx];
//擦除圖片
CGContextClearRect(ctx, rect);
//上下文中拿到圖片一張圖片
UIImage * image = UIGraphicsGetImageFromCurrentImageContext();
self.imageA.image = image;
//關閉上下文
UIGraphicsEndImageContext();
}
- 手勢解鎖
示例
- 手勢解鎖.jpeg
自定義一個九宮格布局按鈕的view,分別這是button的normal和selected背景圖片
view添加一個pan手勢
pan觸發的方法中實現
-(void)pan:(UIPanGestureRecognizer *)pan{
//時刻記錄手指當前的位置
self.fingerPoint = [pan locationInView:self];
for (UIButton * btn in self.subviews) {
//如果手指的位置在btn的范圍內 且這個按鈕沒有被選中過
if (CGRectContainsPoint(btn.frame, self.fingerPoint) && (!btn.selected)) {
//改變按鈕狀態為選中
btn.selected = YES;
//加入選中數組中
[self.selectsBtn addObject:btn];
}
}
//滑動結束后重置狀態
if (pan.state == UIGestureRecognizerStateEnded) {
// 創建可變字符串
NSMutableString *strM = [NSMutableString string];
// 保存輸入密碼
for (UIButton *btn in self.selectsBtn) {
[strM appendFormat:@"%ld",btn.tag];
}
//記錄密碼
NSLog(@"%@",strM);
// 還原界面
// 取消所有按鈕的選中
// [self.selectsBtn makeObjectsPerformSelector:@selector(setSelected:) withObject:@(NO)];
//講所有選中的按鈕狀態改變
[self.selectsBtn enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
UIButton * btn = obj;
btn.selected=NO;
}];
// 清除畫線,把選中按鈕清空
[self.selectsBtn removeAllObjects];
}
[self setNeedsDisplay];
}
- 畫板
- 一個簡單的畫板示例
畫板.jpeg
- 每個pan事件觸發begin時創建一個路徑,加載到數組中保存
-(void)pan:(UIPanGestureRecognizer *)pan{
//獲得手指的位置
CGPoint fingerPoint = [pan locationInView:self];
//pan 開始時
if (pan.state == UIGestureRecognizerStateBegan) {
//繼承UIBezierPath,增加一個保存顏色的屬性
MyBezierPath * path = [MyBezierPath bezierPath];
//設置線寬
path.lineWidth = self.lineWidth;
//設置顏色
path.pathColor = self.pathColor;
//設置起點
[path moveToPoint:fingerPoint];
self.path = path;
//講路徑加入到數組中保存
[self.paths addObject:path];
}
//滑動時 通過手指位置改變進行連線
[self.path addLineToPoint:fingerPoint];
//重新繪制
[self setNeedsDisplay];
}
- 實現drawRect方法,遍歷數組中保存的每條路徑,進行繪圖
-(void)drawRect:(CGRect)rect{
//遍歷保存的路徑
for (MyBezierPath * path in self.paths) {
if ([path isKindOfClass:[UIImage class]]) {
//如果是圖片繪制圖片
UIImage * image = (UIImage *)path;
[image drawAtPoint:CGPointZero];
}else{
//不是圖片連線
[path.pathColor set];
[path stroke];
}
}
}
- 小功能即對保存路徑的數組進行操作重繪調用drawRect即可
//清空
-(void)clear{
//清楚路徑數組
[self.paths removeAllObjects];
[self setNeedsDisplay];
}
//撤銷
-(void)undo{
//刪除最后路徑數組最后一個元素
[self.paths removeLastObject];
[self setNeedsDisplay];
}
//橡皮擦
-(void)eraser{
//一條白色的路徑,橡皮假象
self.pathColor = [UIColor whiteColor];
}