Quartz2D
簡介
Quartz2D是二維(平面)的繪圖引擎(經包裝的函數庫,方便開發者使用。也就是說蘋果幫我們封裝了一套繪圖的函數庫)
同時支持iOS和Mac系統開發,用Quartz2D寫的同一份代碼,既可以運行在iphone上又可以運行在mac上,可以跨平臺開發。
Quartz 2D能完成的工作
繪制圖形 : 線條\三角形\矩形\圓\弧等
繪制文字
繪制\生成圖片(圖像)
讀取\生成PDF
截圖\裁剪圖片
自定義UI控件
.....
Quartz2D在iOS開發中的價值
為了便于搭建美觀的UI界面,iOS提供了UIKit框架,里面有各種各樣的UI控件 UILabel:顯示文字 UIImageView:顯示圖片 UIButton:同時顯示圖片和文字(能點擊) … …
利用UIKit框架提供的控件,拼拼湊湊,能搭建和現實一些簡單、常見的UI界面,但是,有些UI界面極其復雜、而且比較個性化,用普通的UI控件無法實現,這時可以利用Quartz2D技術將控件內部的結構畫出來,自定義控件的樣子,iOS中大部分控件的內容都是通過Quartz2D畫出來的,因此,Quartz2D在iOS開發中很重要的一個價值是:自定義view(自定義UI控件)
自定義View
圖形上下文(Graphics Context):是一個CGContextRef類型的數據
圖形上下文的作用
保存繪圖信息、繪圖狀態 -決定繪制的輸出目標(繪制到什么地方去?) (輸出目標可以是PDF文件、Bitmap或者顯示器的窗口上) 相同的一套繪圖序列,指定不同的Graphics Context,就可將相同的圖像繪制到不同的目標上
自定義view的步驟
新建一個類,繼承自UIView
實現- (void)drawRect:(CGRect)rect方法,然后在這個方法中
取得跟當前view相關聯的圖形上下文
繪制相應的圖形內容
利用圖形上下文將繪制的所有內容渲染顯示到view上面
drawRect:方法
實現drawRect:方法才能繪圖到view,因為在drawRect:方法中才能取得跟view相關聯的圖形上下文,
drawRect:方法在調用時間 當view第一次顯示到屏幕上時(被加到UIWindow上顯示出來) 調用view的setNeedsDisplay或者setNeedsDisplayInRect:時
在drawRect:方法中取得上下文后,就可以繪制東西到view上
View內部有個layer(圖層)屬性,drawRect:方法中取得的是一個Layer Graphics Context,因此,繪制的東西其實是繪制到view的layer上去了,View之所以能顯示東西,完全是因為它內部的layer
示例1畫直線
1> 獲取圖形上下文 CG:表示這個類在CoreGraphics框架里 Ref:引用 目前學的上下文都跟UIGraphics有關,想獲取圖形上下文,首先敲UIGraphics。
2> 拼接路徑:一般開發中用貝塞爾路徑,里面封裝了很多東西,可以幫我畫一些基本的線段,矩形,圓等等。 創建貝塞爾路徑 起點:moveToPoint 終點:addLineToPoint
3> 把路徑添加到上下文 CGPath轉換:UIKit框架轉CoreGraphics直接CGPath就能轉
4> 把上下文渲染到視圖,圖形上下文本身不具備顯示功能。 PPT畫圖分析為什么要這樣做?首先獲取圖形上下文,然后描述路徑,把路徑添加到上下文,渲染到視圖,圖形上下文相當于一個內存緩存區,在內存里面操作是最快的,比直接在界面操作快多了。
在添加一根線 直接addLineToPoint,因為路徑是拼接的,默認下一條線的起點是上一條線的終點。
畫兩跟不連接的線 1> 第二次畫的時候,重新設置起點,然后畫線。一個路徑可以包含多條線段。 2> 新創建一個路徑,添加到上下文。開發中建議使用這種,比較容易控制每根線。
設置繪圖狀態 線段怎么加粗。 繪圖狀態調用順序:只要在渲染之前就好了,在渲染的時候才會去看繪圖的最終狀態。
// 1.獲取跟當前view想關聯的上下文// 以后只要根上下文有關,直接敲UIGraphics,一般都是以UIGraphics開頭// CG:CoreGraphics Ref:引用CGContextRefctx =UIGraphicsGetCurrentContext();// 2.繪制內容,拼接路徑,繪制的內容統稱為路徑// 在開發中一般使用貝塞爾路徑,UIKit框架UIBezierPath*path = [UIBezierPathbezierPath];// 設置起點,移動到某個位置[path moveToPoint:CGPointMake(50,50)];// 添加一根線到某個點[path addLineToPoint:CGPointMake(200,200)];// 一根路徑對象可以包含很多線段[path moveToPoint:CGPointMake(50,200)];// 默認下一根線的起點在上一根線的終點[path addLineToPoint:CGPointMake(100,200)];// 3.把路徑添加到上下文,給上下文添加路徑以CGContextCGContextAddPath(ctx, path.CGPath);// 開發中,如果線段不連接,最好使用一根線對應一個路徑對象// 描述第二根線//? ? path = [UIBezierPath bezierPath];////? ? // 設置起點,移動到某個位置//? ? [path moveToPoint:CGPointMake(200, 200)];////? ? // 添加一根線到某個點//? ? [path addLineToPoint:CGPointMake(100, 200)];////? ? CGContextAddPath(ctx, path.CGPath);// 4.把上下文內容渲染到viewCGContextStrokePath(ctx);
示例2畫圓形
//Center:圓心//radius:半徑.//startAngle:開始角度//endAngle:結束角度//clockwise:CGPointcenter =CGPointMake(self.bounds.size.width*0.5,self.bounds.size.height*0.5);CGFloatradius =100;CGFloatstartA =0;//圓的0度角在圓的最右側.CGFloatendA = -M_PI_2;UIBezierPath*path =? [UIBezierPathbezierPathWithArcCenter:center radius:radius startAngle:startA endAngle:endA clockwise:YES];? ? [UIColorredColor] set];? ? [path stroke];
示例3畫文字,常見屬性
//我們要畫的文字NSString*str =@"我們要畫的文字";//開始繪制文字//? ? AtPoint:畫在哪個點上.//Attributes:文字的屬性.顏色,字體大小...NSMutableDictionary*dict = [NSMutableDictionarydictionary];//設置文字的顏色dict[NSForegroundColorAttributeName] = [UIColorredColor];//設置字體大小dict[NSFontAttributeName] = [UIFontsystemFontOfSize:50];//設置描邊的顏色dict[NSStrokeColorAttributeName] = [UIColorblueColor];//設置描邊的寬度dict[NSStrokeWidthAttributeName] = @1;NSShadow*shadow = [[NSShadowalloc] init];//設置陰影的偏移量shadow.shadowOffset=CGSizeMake(-10,10);//設置陰影的顏色shadow.shadowColor= [UIColorgreenColor];//設置陰影的模糊shadow.shadowBlurRadius=2;? ? dict[NSShadowAttributeName] = shadow;//drawAtPoint不會自動換行//? ? [str drawAtPoint:CGPointZero withAttributes:dict];//drawInRect它會自動的換行.[str drawInRect:self.boundswithAttributes:dict];
圖形上下文的矩陣操作
形變操作,必須得要在添加路徑之前進行
計時器
CADisplayLink
//在繪圖當中, 我們一般使用CADisplayLink.因為他和setNeedsDisplay調用時機是一樣的,都是當下一次屏幕刷新的時候調用.
//CADisplayLink//它是當每次屏幕刷新的時候就會調用定時器方法.(每一秒種刷新60次)CADisplayLink*link = [CADisplayLinkdisplayLinkWithTarget:selfselector:@selector(update)];//想讓定時器工作,必須得要把它添加到主運行循環.[link addToRunLoop:[NSRunLoopmainRunLoop] forMode:NSDefaultRunLoopMode];
NSTimer
[NSTimerscheduledTimerWithTimeInterval:0.01target:selfselector:@selector(update) userInfo:nilrepeats:YES];
注意,在實際開發中視情況選擇使用
常用的拼接函數
新建一個起點voidCGContextMoveToPoint(CGContextRefc,CGFloatx,CGFloaty)添加新的線段到某個點voidCGContextAddLineToPoint(CGContextRefc,CGFloatx,CGFloaty)添加一個矩形voidCGContextAddRect(CGContextRefc,CGRectrect)添加一個橢圓voidCGContextAddEllipseInRect(CGContextRefcontext,CGRectrect)添加一個圓弧voidCGContextAddArc(CGContextRefc,CGFloatx,CGFloaty,CGFloatradius,CGFloatstartAngle,CGFloatendAngle,intclockwise)
常用繪制的函數
Mode參數決定繪制的模式
voidCGContextDrawPath(CGContextRefc,CGPathDrawingModemode)繪制空心路徑voidCGContextStrokePath(CGContextRefc)繪制實心路徑voidCGContextFillPath(CGContextRefc)提示:一般以CGContextDraw、CGContextStroke、CGContextFill開頭的函數,都是用來繪制路徑的
矩陣操作
利用矩陣操作,能讓繪制到上下文中的所有路徑一起發生變化縮放voidCGContextScaleCTM(CGContextRefc,CGFloatsx,CGFloatsy)旋轉voidCGContextRotateCTM(CGContextRefc,CGFloatangle)平移voidCGContextTranslateCTM(CGContextRefc,CGFloattx,CGFloatty)
圖片水印技術
需求:在手機客戶端app中需要用到水印技術,用戶拍完照片后,可以在照片上打個水印,標識這個圖片是屬于哪個用戶的
實現方式:利用Quartz2D,將水印(文字、LOGO)畫到圖片的右下角
核心代碼
開啟一個基于位圖的圖形上下文UIGraphicsBeginImageContextWithOptions(CGSizesize,BOOLopaque,CGFloatscale)從上下文中取得圖片(UIImage)UIImage*UIGraphicsGetImageFromCurrentImageContext();結束基于位圖的圖形上下文UIGraphicsEndImageContext();
水印PPT簡介
圖片水印作用:防止他人盜取圖片,加一些Logo,生成一張新的圖片。
和繪圖一樣的生成新的圖片,需要拿到上下文做事情,這里也需要拿到上下文,生成一個新的圖片。
位圖上下文,在這個上下文畫東西,就能輸出到新的圖片上。
之前用的都是圖層上下文,系統會自動創建,但是我們位圖上下文,需要我們手動創建
總結:只要不和view有關系的上下文,都需要我們手動創建。
在哪獲取圖像上下文,viewDidLoad, 不需要拿到系統創建的圖層上下文,沒必要在drawRect方法里寫,直接viewDidLoad就行了。
UIGraphicsBeginImageContextWithOptions:(CGSize size, BOOL opaque, CGFloat scale)創建一個位圖上下文,而且這種方法得到的圖片最清晰。解釋參數(size:新圖片尺寸 opaque: YES:不透明 NO:透明 scale:0.0 不伸縮)
繪制內容(圖片,文字)
獲取圖片:把位圖上下文的內容生成一個圖片給你。
關閉上下文,不關閉一直占用著內存。
顯示UIImageView上
保存圖片,寫到文件,UIImage不能寫,需要轉換成NSData二進制數據
UIImageJPEGRepresentation:可以設置圖片質量
UIImagePNGRepresentation:把圖片轉換成png格式的二進制數據,png格式默認是最高清的。
寫到桌面
圖片裁剪
圖片裁剪
PPT分析思路:先設置裁剪區域,把圖片畫上去,超出裁剪區域的自動裁剪掉。
加載舊圖片,根據舊圖片,獲取上下文尺寸。
上下文的尺寸 = 新圖片的尺寸
開啟一個多大的上下文?:和圖片尺寸一樣大,避免壓縮圖片。如果比圖片尺寸小,會壓縮圖片。
設置裁剪區域:正切于圖片的圓
繪制舊圖片
獲取新圖片
關閉上下文
//1.加載要裁剪的圖片UIImage*image = [UIImageimageNamed:@"阿貍頭像"];
//2.開啟一個跟圖片尺寸一樣大的位圖上下文UIGraphicsBeginImageContextWithOptions(image.size,NO,0);
//3.設置一個裁剪(寬度和高度都是等于原始圖片的寬高)UIBezierPath*path = [UIBezierPathbezierPathWithOvalInRect:CGRectMake(0,0, image.size.width, image.size.height)];//把路徑設置成裁剪區域[path addClip];
//4.把要裁剪的圖片繪制到上下文當中.[image drawAtPoint:CGPointZero];
//5.從上下文當中生成一張新的圖片UIImage*newImage =UIGraphicsGetImageFromCurrentImageContext();
//6.關閉上下文.UIGraphicsEndImageContext();self.imageV.image= newImage;
帶圓環裁剪:在裁剪的圖片外邊加個小圓環。
先畫一個大圓,在設置裁剪區域,把圖片畫上去,超出裁剪區域的自動裁剪掉。
加載舊圖片,根據舊圖片,獲取上下文尺寸。
確定圓環寬度 borderW
上下文的尺寸 = 新圖片的尺寸
確定新的上下文尺寸: newImageW : oldImageW + 2borderW newImageH : oldImageH + 2borderW,
繪制大圓: 1.獲取上下文 2.添加路徑到上下文 3.設置大圓的顏色 = 圓環的顏色 4.渲染
設置裁剪區域,和圖片尺寸一樣大,只不過,x,y不一樣,x=borderW,y=borderW.
繪制舊圖片
獲取新圖片
關閉上下文
抽分類,3個參數,圖片名稱,圓環寬度,圓環顏色
可以創建給UIImage添加一個分類提供方法直接使用
//0.設置一個邊框寬度//1.加載圖片//2.開啟一個位圖上下文CGSizesize =CGSizeMake(image.size.width+2* boderW, image.size.height+2* boderW);UIGraphicsBeginImageContextWithOptions(size,NO,0);
//3.設置一個大圓的一個填充區域UIBezierPath*path = [UIBezierPathbezierPathWithOvalInRect:CGRectMake(0,0, size.width, size.height)];//設置邊框的顏色[color set];? ? [path fill];
//4.設置一個小圓裁剪區域path = [UIBezierPathbezierPathWithOvalInRect:CGRectMake(boderW, boderW, image.size.width, image.size.height)];//把小圓設置成裁剪區域[path addClip];
//5.把原始圖片繪制到上下文當中[image drawInRect:CGRectMake(boderW, boderW, image.size.width, image.size.height)];
//6.生成一張新的圖片UIImage*newImage =UIGraphicsGetImageFromCurrentImageContext();
//7.關閉上下文.UIGraphicsEndImageContext();returnnewImage;
圖片截屏
屏幕截圖:把屏幕的內容截屏生成一張新的圖片
通常開發中,都是把控制器的內容截屏,生成新的圖片
控制器根據view顯示
view根據layer圖層顯示
把layer渲染到位圖上下文
注意:圖層只能用渲染,圖片和文字可以用draw
渲染在新的圖片
開啟圖片上下文,和視圖一樣的尺寸
寫入桌面
抽分
//懶加載,當使用才創建,可以節省內存-(UIView*)cover{if(_cover ==nil) {//創建一個遮蓋UIView*cover = [[UIViewalloc] init];? ? ? ? cover.backgroundColor= [UIColorblackColor];? ? ? ? cover.alpha=0.7;? ? ? ? _cover = cover;? ? ? ? [self.viewaddSubview:cover];? ? }return_cover;}
- (void)viewDidLoad {? ? [superviewDidLoad];// Do any additional setup after loading the view, typically from a nib.}
//當手指拖動的時候調用- (IBAction)pan:(UIPanGestureRecognizer*)pan {//獲取當前手指所在的點CGPointcurP = [pan locationInView:self.imageV];//起點if(pan.state==UIGestureRecognizerStateBegan){//添加遮蓋,設置遮蓋的起點.self.startP= curP;? ? }elseif(pan.state==UIGestureRecognizerStateChanged){//移動,確定遮蓋的尺寸//x軸偏移量CGFloatoffsetX = curP.x-self.startP.x;//Y 軸的偏移量CGFloatoffsetY = curP.y-self.startP.y;//改變遮蓋的尺寸self.cover.frame=CGRectMake(self.startP.x,self.startP.y, offsetX, offsetY);? ? }else if(pan.state==UIGestureRecognizerStateEnded){//松開//做裁剪
//開啟一個上下文.(跟圖片一樣大的上下文.)UIGraphicsBeginImageContextWithOptions(self.imageV.bounds.size,NO,0);
//在上下文當中設置一個裁剪區域(就是當前遮蓋的frame)UIBezierPath*path = [UIBezierPathbezierPathWithRect:self.cover.frame];
//設置裁剪區域[path addClip];//把UIImageView當中的圖片繪制到上下文當中.
//獲取當前上下文CGContextRefctx =UIGraphicsGetCurrentContext();? ? ? [self.imageV.layerrenderInContext:ctx];
//生成一張新的圖片.UIImage*newImage =UIGraphicsGetImageFromCurrentImageContext();
//把位圖上下文關閉掉UIGraphicsEndImageContext();
//把UIImageView當中的圖片重新賦值self.imageV.image= newImage;//移除遮蓋[self.coverremoveFromSuperview];? ? }}
手勢解鎖
分析界面有幾個控件:背景:UIImageView 白色圓圈:按鈕(點擊他,會出現另外一種圖片,按鈕可以設置不同狀態下的圖片。)單獨視圖:(畫線是有范圍的,當超出view就不能畫線了)
HMLoadView:自定義視圖,在視圖一創建的時候,就添加9個按鈕。
在initWithCoder,initWithFrame方法添加按鈕。
九宮格布局:
tolcol =3計算row,col 按鈕的x,y跟col,row有關系,col = i % tolcol row = i / tolcol? 計算邊距 margin = (view.bounds.size.width- tolcol * btnW) / (tolcol +1)? btnX = margin + (btnW + margin) * col? btnY = (btnW + margin) * row
圓的選中
點擊按鈕就為選中的圖片怎么做?監聽按鈕點擊。
不能addTarget: 不能及時顯示選中圖片。
監聽touchBegin,判斷點在不在按鈕的frame上。
touchBegin不調用?原因:事件交給按鈕處理,應該把事件交給解鎖視圖。讓按鈕不接收事件。
設置按鈕不允許交互,2個用處:1.不接收事件 2.取消高亮效果 一舉兩得
遍歷所有按鈕,看觸摸點在哪個按鈕上,就選中誰,CGRectContainsPoint 傳入的參數必須是同一個坐標系
實現touchMove方法:因為手指移動的時候,也需要判斷點在不在按鈕上。
抽方法,因為touchMove的方法里,也需要做同樣的事情。
1>pointWithTouches根據touches集合取出觸摸點2>buttonWithPoint根據觸摸點,獲取觸摸按鈕
圓的連線
被選中按鈕之間都需要連線,還有一個多余的線
先把選中的按鈕全部連線,因為多余的那根線是從最后一個按鈕的圓心開始畫,手指移動在哪就畫哪。
搞個數組保存下所有選中按鈕,在drawRect方法中遍歷所有按鈕,連線
UIBezierPath畫線,不需要上下文。
需要多少個UIBezierPath對象?一個,路徑都是連續的,不相連的,才需要創建新的UIBezierPath。
遍歷數組,描述路徑 1> 起點:第一個按鈕的圓心 2> 添加一根線到其他按鈕的圓心
設置路徑的顏色和線寬
把所有路徑都描述完就,渲染一次就夠了。
[path stroke] 就能渲染到視圖上了。
畫多余的那根線,記住手指移動的位置。
setNeedDisplay 因為drawRect只會調用一次,需要每次手指移動的時候,都需要重繪。
touchBegin不調用setNeedDisplay,因為重繪也沒用,只有起點。
lineJoinStyle:有尖尖的東西,線段連接樣式的問題,設置為平的。
已經選中的按鈕,不需要再次選中,和畫線
手指抬起,取消所有選中按鈕,并且清空數組,清空線條
drawRect判斷下沒有選中數組,不需要畫線。
如何判斷用戶是否輸入正確?給選中按鈕綁定tag,遍歷所有選中按鈕,把tag拼接成一個字符串。
畫板
分析控件:ToolBar(不需要管里面子控件的frame),畫板view,自定義工具條(方便屏幕適配,能迅速固定里面子控件的位置)
自動布局,四個約束確定一個控件
藍色按鈕:左,右,下,高固定
橘色按鈕:右,下固定,寬度和高度和藍色按鈕相等。
綠色按鈕:右,下,寬度和高度和橘色按鈕相等
繪圖思路:先描述路徑,在渲染,需要畫很多線條,最好每個線條保存到一個路徑里面。
繪圖功能
touchBegin設置畫線起點:開始觸摸的點
創建UIBezierPath,貝塞爾路徑才能設置起點
touchMove:手指移動到哪就畫哪,addLine到移動的點
setNeedDisplay,路徑描述完了,就渲染到視圖就好了。
drawRect方法每次都會把之前的清掉,重新繪制
搞個數組保存上一次的,繪制多條線
設置線寬:每次滑動滑塊,就改變下一次路徑的寬度
監聽滑塊的值,把值傳遞給paintView,設置路徑的線寬。
不能在drawRect寫,會導致所有路徑都是一個線寬,應該是每個路徑都記錄自己的線寬,而且線寬只需要設置一次,在路徑一創建的時候就設置。
設置顏色:
自定義UIBezierPath,保存顏色,實現一條路徑對應一個顏色 輔助功能:
清屏:清空所有路徑數組
撤銷:移除最后一條路徑
橡皮擦:設置畫筆為白色
保存:
1> 把畫板內容截屏
2> 把圖片保存到相冊 UIImageWriteToSavedPhotosAlbum
3> 保存相冊的回調方法不能亂寫,必須按照規定 image:didFinishSavingWithError:contextInfo:
照片選擇
1> 通常都是去相冊里去照片
2> UIImagePickerController,就可以去手機相冊了
3> 用Modal,沒有導航控制器,不能push
4> 設置代理,獲取圖片
5> 把圖片傳遞給paintView
6> 添加到路徑,然后重繪。:畫圖片也需要順序的
照片處理
1>搞一個和畫板一樣的透明view,里面搞個UIImageView來顯示我們從照片庫選擇的圖片,然后對UIImageView進行旋轉,縮放等操作
2> UImageView不能放在layoutSubViews里面設置尺寸,因為要設置他的形變,默認會調用他父類的layoutSubViews,導致一些莫名其妙的原因
3> 在傳圖片的時候設置他的尺寸,和位置,讓他和圖片一樣的尺寸,顯示在中間
4> 長按操作:在長按結束的時候,做操作
1.默認高亮狀態,先變淺在恢復,設置alpha
2.動畫結束后,把自己截屏,傳給控制器里,在交給paintView顯示
3.需要移除父視圖,使命完成了,而且不移除,不能繪制東西,永遠添加到paintView上面