iOS开发之Quartz 2D绘图
2019獨角獸企業重金招聘Python工程師標準>>>
Quartz 2D是一個二維圖形繪制引擎,支持iOS環境和Mac OS X環境
Quartz 2D API可以實現許多功能,如基于路徑的繪圖、透明度、陰影、顏色管理、反鋸齒、PDF文檔生成和PDF元數據訪問等
Quartz 2D API是Core Graphics框架的一部分,因此其中的很多數據類型和方法都是以CG開頭的。會經常見到Quartz 2D(Quartz)和Core Graphics兩個術語交互使用
Quartz 2D與分辨率和設備無關,因此在使用Quartz 2D繪圖時,無需考慮最終繪圖的目標設備
Core Graphic框架是一組基于C的API,可以用于一切繪圖操作,這個框架的重要性,僅次于UIKit和Foundation
當使用UIKit創建按鈕、標簽或者其他UIView的子類時,UIKit會用Core Graphics將這些元素繪制在屏幕上。此外,UIEvent(UIKit中的事件處理類)也會使用Core Graphics,用來幫助確定觸摸事件在屏幕上所處的位置
因為UIKit依賴于Core Graphics,所以當引入<UIKit/Uikit.h>時,Core Graphics框架會被自動引入,即UIKit內部已經引入了Core Graphics框架的主頭文件:<CoreGraphics/CoreGraphics.h>
為了讓開發者不必觸及底層的Core Graphics的C接口,UIKit內部封裝了Core Graphics的一些API,可以快速生成通用的界面元素。但是,有時候直接利用Core Graphics的C接口是很有必要和很有好處的,比如創建一個自定義的界面元素。
Quartz 2D繪圖的基本步驟:
1. 獲取與視圖相關聯的上下文對象
UIGraphicsGetCurrentContext
2. 創建及設置路徑 (path)
2.1 創建路徑
2.2 設置路徑起點
2.3 增加路徑內容……
3. 將路徑添加到上下文
4. 設置上下文狀態
邊線顏色、填充顏色、線寬、線段連接樣式、線段首尾樣式、虛線樣式…
5. 繪制路徑
6. 釋放路徑
接下來實現一個畫板:
#import <Foundation/Foundation.h>@interface DrawPath : NSObject+ (id)drawPathWithCGPath:(CGPathRef)drawPathcolor:(UIColor *)colorlineWidth:(CGFloat)lineWidth;@property (strong, nonatomic) UIBezierPath *drawPath; @property (strong, nonatomic) UIColor *drawColor; @property (assign, nonatomic) CGFloat lineWith;// 用戶選擇的圖像 @property (strong, nonatomic) UIImage *image;@end#import "DrawPath.h"@implementation DrawPath+ (id)drawPathWithCGPath:(CGPathRef)drawPathcolor:(UIColor *)colorlineWidth:(CGFloat)lineWidth {DrawPath *path = [[DrawPath alloc]init];path.drawPath = [UIBezierPath bezierPathWithCGPath:drawPath];path.drawColor = color;path.lineWith = lineWidth;return path; }@end
#import "DrawView.h" #import "DrawPath.h"@interface DrawView()// 當前的繪圖路徑 @property (assign, nonatomic) CGMutablePathRef drawPath; // 繪圖路徑數組 @property (strong, nonatomic) NSMutableArray *drawPathArray;// 標示路徑是否被釋放 @property (assign, nonatomic) BOOL pathReleased;@end@implementation DrawView- (id)initWithFrame:(CGRect)frame {self = [super initWithFrame:frame];if (self) {[self setBackgroundColor:[UIColor whiteColor]];// 設置屬性的初始值self.lineWidth = 10.0;self.drawColor = [UIColor redColor];}return self; }#pragma mark - 繪制視圖 // 注意:drawRect方法每次都是完整的繪制視圖中需要繪制部分的內容 - (void)drawRect:(CGRect)rect {// 1. 獲取上下文CGContextRef context = UIGraphicsGetCurrentContext();[self drawView:context]; }#pragma mark 繪圖視圖內容的方法 - (void)drawView:(CGContextRef)context {// 首先將繪圖數組中的路徑全部繪制出來for (DrawPath *path in self.drawPathArray) {if (path.image == nil) {CGContextAddPath(context, path.drawPath.CGPath);[path.drawColor set];CGContextSetLineWidth(context, path.lineWith);CGContextSetLineCap(context, kCGLineCapRound);CGContextDrawPath(context, kCGPathStroke);} else {// 有圖像,沒路徑 // CGContextDrawImage(context, self.bounds, path.image.CGImage);[path.image drawInRect:self.bounds];}}//--------------------------------------------------------// 以下代碼繪制當前路徑的內容,就是手指還沒有離開屏幕// 內存管理部分提到,所有create創建的都要release,而不能設置成NULLif (!self.pathReleased) { // 1. 添加路徑CGContextAddPath(context, self.drawPath);// 2. 設置上下文屬性[self.drawColor set];CGContextSetLineWidth(context, self.lineWidth);CGContextSetLineCap(context, kCGLineCapRound);// 3. 繪制路徑CGContextDrawPath(context, kCGPathStroke);} }#pragma mark - 觸摸事件 #pragma mark 觸摸開始,創建繪圖路徑 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {UITouch *touch = [touches anyObject];CGPoint location = [touch locationInView:self];self.drawPath = CGPathCreateMutable();// 記錄路徑沒有被釋放self.pathReleased = NO;// 在路徑中記錄觸摸的初始點CGPathMoveToPoint(self.drawPath, NULL, location.x, location.y); }#pragma mark 移動過程中,將觸摸點不斷添加到繪圖路徑 - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {// 可以獲取到用戶當前觸摸的點UITouch *touch = [touches anyObject];CGPoint location = [touch locationInView:self];// 將觸摸點添加至路徑CGPathAddLineToPoint(self.drawPath, NULL, location.x, location.y);[self setNeedsDisplay]; }#pragma mark 觸摸結束,釋放路徑 - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {// 一筆畫完之后,將完整的路徑添加到路徑數組之中// 使用數組的懶加載if (self.drawPathArray == nil) {self.drawPathArray = [NSMutableArray array];}// 要將CGPathRef添加到NSArray之中,需要借助貝塞爾曲線對象// 貝塞爾曲線是UIKit對CGPathRef的一個封裝,貝塞爾路徑的對象可以直接添加到數組 // UIBezierPath *path = [UIBezierPath bezierPathWithCGPath:self.drawPath];DrawPath *path = [DrawPath drawPathWithCGPath:self.drawPath color:self.drawColor lineWidth:self.lineWidth];// 需要記錄當前繪制路徑的顏色和線寬[self.drawPathArray addObject:path];CGPathRelease(self.drawPath);// 標示路徑已經被釋放self.pathReleased = YES;// 測試線寬的代碼 // self.lineWidth = arc4random() % 20 + 1.0; }#pragma mark - 工具視圖執行方法 - (void)undo {// 在執行撤銷操作時,當前沒有繪圖路徑// 要做撤銷操作,需要把路徑數組中的最后一條路徑刪除[self.drawPathArray removeLastObject];[self setNeedsDisplay]; }#pragma mark - 清屏操作 - (void)clearScreen {// 在執行清屏操作時,當前沒有繪圖路徑// 要做清屏操作,只要把路徑數組清空即可[self.drawPathArray removeAllObjects];[self setNeedsDisplay]; }#pragma mark - image 的 setter方法 - (void)setImage:(UIImage *)image {/*目前繪圖的方法:1> 用self.drawPathArray記錄已經完成(抬起手指)的路徑2> 用self.drawPath記錄當前正在拖動中的路徑繪制時,首先繪制self.drawPathArray,然后再繪制self.drawPathimage 傳入時,drawPath沒有被創建(被release但不是NULL)如果1> 我們將image也添加到self.drawPathArray(DrawPath)2> 在繪圖時,根據是否存在image判斷是繪制路徑還是圖像就可以實現用一個路徑數組即繪制路徑,又繪制圖像的目的之所以要用一個數組,是因為繪圖是有順序的接下來,首先需要擴充DrawPath,使其支持image*/// 1. 實例化一個新的DrawPathDrawPath *path = [[DrawPath alloc]init];[path setImage:image];// 2. 將其添加到self.drawPathArray,數組是懶加載的if (self.drawPathArray == nil) {self.drawPathArray = [NSMutableArray array];}[self.drawPathArray addObject:path];// 3. 重繪[self setNeedsDisplay]; }@end #import <UIKit/UIKit.h>@interface MyButton : UIButton@property (assign, nonatomic) BOOL selectedMyButton;@end #import "MyButton.h"@implementation MyButton- (id)initWithFrame:(CGRect)frame {self = [super initWithFrame:frame];if (self) {[self.titleLabel setFont:[UIFont systemFontOfSize:12.0]];[self setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];}return self; }- (void)drawRect:(CGRect)rect {// 如果selectedMyButton == YES在按鈕的下方繪制一條紅線if (self.selectedMyButton) {CGRect frame = CGRectMake(0, self.bounds.size.height - 2, self.bounds.size.width, 2);[[UIColor redColor]set];UIRectFill(frame);} }#pragma mark - setter方法 - (void)setSelectedMyButton:(BOOL)selectedMyButton {_selectedMyButton = selectedMyButton;[self setNeedsDisplay]; }@end #import <UIKit/UIKit.h>#pragma mark - 定義塊代碼 typedef void(^SelectColorBlock)(UIColor *color);@interface SelectColorView : UIView// 擴展initWithFrame方法,增加塊代碼參數 // 該塊代碼,將在選擇顏色按鈕之后執行 - (id)initWithFrame:(CGRect)frame afterSelectColor:(SelectColorBlock)afterSelectColor;@end #import "SelectColorView.h"#define kButtonSpace 10.0@interface SelectColorView() {// 選擇顏色的塊代碼變量SelectColorBlock _selectColorBlock; }@property (strong, nonatomic) NSArray *colorArray;@end@implementation SelectColorView- (id)initWithFrame:(CGRect)frame afterSelectColor:(SelectColorBlock)afterSelectColor {self = [super initWithFrame:frame];if (self) {_selectColorBlock = afterSelectColor;[self setBackgroundColor:[UIColor lightGrayColor]];// 繪制顏色的按鈕NSArray *array = @[[UIColor darkGrayColor],[UIColor redColor],[UIColor greenColor],[UIColor blueColor],[UIColor yellowColor],[UIColor orangeColor],[UIColor purpleColor],[UIColor brownColor],[UIColor blackColor],];self.colorArray = array;[self createColorButtonsWithArray:array];}return self; }#pragma mark - 繪制顏色按鈕 - (void)createColorButtonsWithArray:(NSArray *)array {// 1. 計算按鈕的位置// 2. 設置按鈕的顏色,需要使用數組// 按鈕的寬度,起始點位置NSInteger count = array.count;CGFloat width = (self.bounds.size.width - (count + 1) * kButtonSpace)/ count;CGFloat height = self.bounds.size.height;NSInteger index = 0;for (NSString *text in array) {UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];CGFloat startX = kButtonSpace + index * (width + kButtonSpace);[button setFrame:CGRectMake(startX, 5, width, height - 10)];// 設置按鈕的背景顏色[button setBackgroundColor:array[index]];[button setTag:index];[button addTarget:self action:@selector(tapButton:) forControlEvents:UIControlEventTouchUpInside];[self addSubview:button];index++;} }#pragma mark - 按鈕監聽方法 - (void)tapButton:(UIButton *)button {// 調用塊代碼_selectColorBlock(self.colorArray[button.tag]); }@end #import <UIKit/UIKit.h>#pragma mark - 定義塊代碼 typedef void(^SelectLineWidthBlock)(CGFloat lineWidth);@interface SelectLineWidthView : UIView#pragma mark 擴展初始化方法,增加塊代碼 - (id)initWithFrame:(CGRect)frame afterSelectLineWidth:(SelectLineWidthBlock)afterSeletLineWidth;@end #import "SelectLineWidthView.h" // 針對不同的界面,因為按鈕的數量是不同的,有時候需要調整按鈕間距,保證好的視覺效果 #define kButtonSpace 10.0@interface SelectLineWidthView() {SelectLineWidthBlock _selectLineWidthBlock; }@property (strong, nonatomic) NSArray *lineWidthArray;@end@implementation SelectLineWidthView- (id)initWithFrame:(CGRect)frame afterSelectLineWidth:(SelectLineWidthBlock)afterSeletLineWidth {self = [super initWithFrame:frame];if (self) {_selectLineWidthBlock = afterSeletLineWidth;[self setBackgroundColor:[UIColor redColor]];[self setBackgroundColor:[UIColor lightGrayColor]];// 繪制顏色的按鈕NSArray *array = @[@(1.0), @(3.0), @(5.0), @(8.0), @(10.0), @(15.0), @(20.0)];self.lineWidthArray = array;[self createLineButtonsWithArray:array];}return self; }#pragma mark 創建選擇線寬的按鈕 - (void)createLineButtonsWithArray:(NSArray *)array {// 1. 計算按鈕的位置// 2. 設置按鈕的顏色,需要使用數組// 按鈕的寬度,起始點位置NSInteger count = array.count;CGFloat width = (self.bounds.size.width - (count + 1) * kButtonSpace)/ count;CGFloat height = self.bounds.size.height;NSInteger index = 0;for (NSString *text in array) {UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];CGFloat startX = kButtonSpace + index * (width + kButtonSpace);[button setFrame:CGRectMake(startX, 5, width, height - 10)];// 設置選擇線寬的提示文字NSString *text = [NSString stringWithFormat:@"%@點", self.lineWidthArray[index]];[button setTitle:text forState:UIControlStateNormal];[button.titleLabel setFont:[UIFont systemFontOfSize:15]];[button setTag:index];[button addTarget:self action:@selector(tapButton:) forControlEvents:UIControlEventTouchUpInside];[self addSubview:button];index++;} }#pragma mark - 按鈕監聽方法 - (void)tapButton:(UIButton *)button {// 把按鈕對應的線寬數值作為塊代碼的參數,執行塊代碼_selectLineWidthBlock([self.lineWidthArray[button.tag]floatValue]); }@end #import <UIKit/UIKit.h> #import "SelectColorView.h" #import "SelectLineWidthView.h"#pragma mark 工具視圖的操作塊代碼 typedef void(^ToolViewActionBlock)();//#pragma mark - 選擇橡皮擦的塊代碼定義 //typedef void(^ToolViewSelectEarserBlock)(); //#pragma mark - 選擇撤銷操作的塊代碼 //typedef void(^ToolViewSelectUndoBlock)(); //#pragma mark - 選擇清屏操作的塊代碼 //typedef void(^ToolViewSelectClearBlock) (); //#pragma mark - 選擇相機的塊代碼 //typedef void(^ToolViewSelectPhotoBlock) ();@interface ToolView : UIView// 對initWithFrame進行擴展,增加塊代碼參數 - (id)initWithFrame:(CGRect)frame afterSelectColor:(SelectColorBlock)afterSelectColor afterSelectLineWidth:(SelectLineWidthBlock)afterSelectLineWidthselectEarser:(ToolViewActionBlock)selectEarserseletUndo:(ToolViewActionBlock)selectUndoselectClear:(ToolViewActionBlock)selectClearselectPhoto:(ToolViewActionBlock)selectPhoto;@end #import "ToolView.h" #import "MyButton.h"// 按鈕的間距 #define kButtonSpace 10.0// 注意,枚舉的順序需要和按鈕文字數組中的順序保持一致 typedef enum {kButtonColor = 0,kButtonLineWidth,kButtonEarser,kButtonUndo,kButtonClearScreen,kButtonCamera,kButtonSave } kButtonActionType;@interface ToolView() {SelectColorBlock _selectColorBlock;SelectLineWidthBlock _selectLineWidthBlock;ToolViewActionBlock _selectEarserBlock;ToolViewActionBlock _selectUndoBlock;ToolViewActionBlock _selectClearBlock;ToolViewActionBlock _selectPhotoBlock; }// 當前用戶選中的按鈕 @property (weak, nonatomic) MyButton *selectButton; // 選擇顏色視圖 @property (weak, nonatomic) SelectColorView *colorView; // 選擇線寬視圖 @property (weak, nonatomic) SelectLineWidthView *lineWidthView;@end@implementation ToolView- (id)initWithFrame:(CGRect)frameafterSelectColor:(SelectColorBlock)afterSelectColor afterSelectLineWidth:(SelectLineWidthBlock)afterSelectLineWidthselectEarser:(ToolViewActionBlock)selectEarserseletUndo:(ToolViewActionBlock)selectUndoselectClear:(ToolViewActionBlock)selectClearselectPhoto:(ToolViewActionBlock)selectPhoto {self = [super initWithFrame:frame];if (self) {_selectColorBlock = afterSelectColor;_selectLineWidthBlock = afterSelectLineWidth;_selectEarserBlock = selectEarser;_selectUndoBlock = selectUndo;_selectClearBlock = selectClear;_selectPhotoBlock = selectPhoto;[self setBackgroundColor:[UIColor lightGrayColor]];// 通過循環的方式創建按鈕NSArray *array = @[@"顏色", @"線寬", @"橡皮", @"撤銷", @"清屏", @"相機", @"保存"];[self createButtonsWithArray:array];}return self; }#pragma mark 創建工具視圖中的按鈕 - (void)createButtonsWithArray:(NSArray *)array {// 按鈕的寬度,起始點位置NSInteger count = array.count;CGFloat width = (self.bounds.size.width - (count + 1) * kButtonSpace)/ count;CGFloat height = self.bounds.size.height;NSInteger index = 0;for (NSString *text in array) { MyButton *button = [MyButton buttonWithType:UIButtonTypeCustom];CGFloat startX = kButtonSpace + index * (width + kButtonSpace);[button setFrame:CGRectMake(startX, 8, width, height - 16)];[button setTitle:text forState:UIControlStateNormal];[button setTag:index];[button addTarget:self action:@selector(tapButton:) forControlEvents:UIControlEventTouchUpInside];[self addSubview:button];// 測試代碼 // [button setSelectedMyButton:YES];index++;} }#pragma mark 按鈕監聽方法 - (void)tapButton:(MyButton *)button {// 方法1:遍歷所有的按鈕,將selectedMyButton設置為NO,取消所有的下方紅線// 方法2:在屬性中記錄前一次選中的按鈕,將該按鈕的屬性設置為NOif (self.selectButton != nil && self.selectButton != button) {[self.selectButton setSelectedMyButton:NO];}// 通過設置當前按鈕selectedMyButton屬性,在下方繪制紅線[button setSelectedMyButton:YES];self.selectButton = button;switch (button.tag) {case kButtonColor:// 點擊按鈕的時候強行關閉當前顯示的子視圖[self forceHideView:self.colorView];// 顯示/隱藏顏色選擇視圖[self showHideColorView];break;case kButtonLineWidth:// 點擊按鈕的時候強行關閉當前顯示的子視圖[self forceHideView:self.lineWidthView];// 顯示/隱藏選擇線寬視圖[self showHideLineWidthView];break;case kButtonEarser:// 以變量的方式調用視圖控制器的塊代碼_selectEarserBlock();[self forceHideView:nil];break;case kButtonUndo:_selectUndoBlock();[self forceHideView:nil];break;case kButtonClearScreen:_selectClearBlock();[self forceHideView:nil];break;case kButtonCamera:_selectPhotoBlock();[self forceHideView:nil];break;default:break;} }#pragma mark - 子視圖操作方法 #pragma mark 強行隱藏當前顯示的子視圖 // 如果顯示的視圖與傳入的比較視圖相同,就不再關閉 - (void)forceHideView:(UIView *)compareView {// 1. 用屬性記錄當前顯示的子視圖,強行關閉該視圖即可// 2. 遍歷所有子視圖,如果處于顯示狀態,則將其關閉// 3. 直接判斷子視圖,此方法僅適用于子數圖數量極少UIView *view = nil;if (self.colorView.frame.origin.y > 0) {view = self.colorView;} else if (self.lineWidthView.frame.origin.y > 0) {view = self.lineWidthView;} else {return;}if (view == compareView) {return;}CGRect toFrame = view.frame;CGRect toolFrame = self.frame;toFrame.origin.y = -44;toolFrame.size.height = 44;[UIView animateWithDuration:0.5f animations:^{[self setFrame:toolFrame];[view setFrame:toFrame];}]; }#pragma mark 顯示隱藏指定視圖 - (void)showHideView:(UIView *)view {// 2. 動畫顯示顏色視圖CGRect toFrame = view.frame;// 工具條視圖邊框CGRect toolFrame = self.frame;if (toFrame.origin.y < 0) {// 隱藏的我們顯示toFrame.origin.y = 44;toolFrame.size.height = 88;} else {toFrame.origin.y = -44;toolFrame.size.height = 44;}[UIView animateWithDuration:0.5f animations:^{[self setFrame:toolFrame];[view setFrame:toFrame];}]; }#pragma mark 顯示隱藏選擇線寬視圖 - (void)showHideLineWidthView {// 1. 懶加載選擇線寬視圖if (self.lineWidthView == nil) {SelectLineWidthView *view = [[SelectLineWidthView alloc]initWithFrame:CGRectMake(0, -44, 320, 44) afterSelectLineWidth:^(CGFloat lineWidth) {NSLog(@"%f", lineWidth);_selectLineWidthBlock(lineWidth);// 強行關閉線寬選擇子視圖[self forceHideView:nil];}];[self addSubview:view];self.lineWidthView = view;}[self showHideView:self.lineWidthView]; }#pragma mark 顯示隱藏顏色視圖 - (void)showHideColorView {// 1. 懶加載顏色視圖if (self.colorView == nil) {SelectColorView *view = [[SelectColorView alloc]initWithFrame:CGRectMake(0, -44, 320, 44) afterSelectColor:^(UIColor *color) {// 以函數的方式調用塊代碼變量_selectColorBlock(color);// 選中顏色后,強行關閉顏色選擇子視圖[self forceHideView:nil];NSLog(@"aaah");}];[self addSubview:view];self.colorView = view;}[self showHideView:self.colorView]; }@end #import "MainViewController.h" #import "DrawView.h" #import "ToolView.h"@interface MainViewController ()@property (weak, nonatomic) DrawView *drawView;@end@implementation MainViewController#pragma mark - 實例化視圖 - (void)loadView {self.view = [[UIView alloc]initWithFrame:[UIScreen mainScreen].applicationFrame];;DrawView *drawView = [[DrawView alloc]initWithFrame:self.view.bounds];[self.view addSubview:drawView];self.drawView = drawView;ToolView *toolView = [[ToolView alloc]initWithFrame:CGRectMake(0, 0, 320, 44) afterSelectColor:^(UIColor *color) {// 給繪圖視圖設置顏色[drawView setDrawColor:color];} afterSelectLineWidth:^(CGFloat lineWidth) {// 工具視圖選擇線寬之后,需要執行的代碼[drawView setLineWidth:lineWidth];} selectEarser:^{[drawView setDrawColor:[UIColor whiteColor]];[drawView setLineWidth:30.0];} seletUndo:^{[drawView undo];} selectClear:^{[drawView clearScreen];} selectPhoto:^{// 彈出圖像選擇窗口,來選擇照片UIImagePickerController *picker = [[UIImagePickerController alloc]init];// 1. 設置照片源[picker setSourceType:UIImagePickerControllerSourceTypePhotoLibrary];// 2. 設置代理[picker setDelegate:self];// 3. 顯示[self presentViewController:picker animated:YES completion:nil];}];[self.view addSubview:toolView]; }#pragma mark - 照片選擇代理方法 - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {UIImage *image = info[@"UIImagePickerControllerOriginalImage"];// 設置繪圖視圖[self.drawView setImage:image];// 關閉照片選擇窗口[self dismissViewControllerAnimated:YES completion:nil]; }@end
轉載于:https://my.oschina.net/khakiloveyty/blog/396543
總結
以上是生活随笔為你收集整理的iOS开发之Quartz 2D绘图的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 第一次做巧克力
- 下一篇: C学习if条件判断和for循环