概念:
1.首先要明白所有iOS中見到的所有東西,都是因?yàn)橥ㄟ^UIView內(nèi)部的圖層——CALayer對象;
并且,UIView顯示到屏幕上時(shí),會調(diào)用【 deawRect: 】方法進(jìn)行繪圖。
即:
》UIView負(fù)責(zé):交互、響應(yīng);
》CALayer負(fù)責(zé):展示、繪圖;
但——系統(tǒng)提供的可能無法滿足需求,那么我們就需要到Quartz2D(自定義UI控件)了。
2.圖形上下文CGContextRef三個(gè)作用:
》路徑
》狀態(tài)
》目標(biāo)
3.drawRect:方法
注意:
》與View相關(guān)的【圖形上下文】只能在 drawRect:方法中取得,但重寫 drawRect:方法需要一個(gè)UIView類,控制器類UIViewController中無法得到 drawRect:,往往需要自行創(chuàng)建一個(gè);
》drawRect不能手動(dòng)調(diào)用,有可能獲取不到正確的上下文;
》drawRect的調(diào)用時(shí)機(jī):
? ?①、在View第一次顯示時(shí);
? ?②、重繪:調(diào)用【 view的setNeedsDisplay 】或【setNeedsDisplayInRect:】的時(shí)候;
步驟:
一、搭建UI界面

二、畫筆功能:
1、重寫drawRect方法,需創(chuàng)建一個(gè)UIView類—— MayView,指定sb (storyboard) 中繪圖區(qū)View的class為 MayView;
2、在MayView類中創(chuàng)建一個(gè)可變數(shù)組用于存儲所有的路徑
//存儲路徑的數(shù)組:路徑不只有一條
@property (nonatomic, strong) NSMutableArray *allPaths;
并且進(jìn)行懶加載
#pragma mark - Getter & Setter
//懶加載
- (NSMutableArray *)allPaths {
if (_allPaths == nil) {
_allPaths = [NSMutableArray array];
}
return _allPaths;
}
3、思考:每一條線都有什么狀態(tài),怎么修改?想畫出一條線,怎么做?
a》注意:路徑的顏色的設(shè)置需要在 drawRect:方法中,并且因?yàn)槊恳粭l線都是獨(dú)立的,我們可能為每一條線設(shè)置不同的顏色等狀態(tài)。我們需要為每一個(gè)路徑,創(chuàng)建獨(dú)立的對象,讓它成為一個(gè)獨(dú)立的存在,并且擁有顏色屬性,那么我們可以通類對象來設(shè)置顏色屬性。因此,我們需要?jiǎng)?chuàng)建一個(gè)路徑類;
@interface MayPath : UIBezierPath
//每條路徑自己的顏色狀態(tài)
@property (nonatomic, strong) UIColor *pathColor;
@end
b》畫一條線:至少分兩步走:點(diǎn)擊 和 移動(dòng),所以需要調(diào)用touchesBegan方法和touchesMove方法
3-1.?調(diào)用方法
在MayView類中引入頭文件
#import "MayPath.h"
在“touchesBegan方法”里面創(chuàng)建一條路徑,并且添加到全局的路徑數(shù)組中(為什么每次都創(chuàng)建而不是用全局的?因?yàn)楫嫲宀豢赡苤稽c(diǎn)擊一次,所以我們要為每一次的點(diǎn)擊都創(chuàng)建一個(gè)路徑)
//開始點(diǎn)擊
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent *)event {
//? ? 1.創(chuàng)建路徑
//每次點(diǎn)擊時(shí)候都創(chuàng)建一條MayPath對象路徑
MayPath * path = [MayPath bezierPath];
//? ? 將創(chuàng)建好的路徑添加到路徑數(shù)組中
[self.allPaths addObject:path];
3-2.這一步我們要在MayView.h文件中創(chuàng)建一個(gè)lineWidth屬性。(后面會將它與sb中的slider控件關(guān)聯(lián))
//線寬(設(shè)為公開屬性為外部調(diào)用,與slider控件關(guān)聯(lián))
@property (nonatomic, assign) CGFloat lineWidth;
回到.m文件,設(shè)置線寬,將定于好的lineWidth賦值過來
//? ? 2.設(shè)置線寬
[path setLineWidth:self.lineWidth];
3-3.我們需要回到控制器類ViewController中,引入自定義MayView類;
將MayView作為屬性傳入
#import "MayView.h"。
@interface ViewController ()
//拿到自定義view(畫板),以拿到畫板的屬性
@property (weak, nonatomic) IBOutlet MayView *paintView;
@end
然后,為sb中的slider控件拖一條方法連線,并且在屬性欄中更改它的值

//設(shè)置線寬與UISlider關(guān)聯(lián)
- (IBAction)setLineWidth:(UISlider *)sender {
self.paintView.lineWidth = sender.value;
}
這樣,lineWidth的值會隨著UISlider控件的變化而變化;
同時(shí)我們需要在viewDidLoad中設(shè)置線寬的初始值,不設(shè)置,一開始沒有點(diǎn)擊UISlider,那么默認(rèn)值為0,會無法顯示。
- (void)viewDidLoad {
[super viewDidLoad];
//設(shè)置線寬的初始值
self.paintView.lineWidth = 1;
}
3-4.回到MayView.m中
//? ? 連接處的樣式
[path setLineJoinStyle:kCGLineJoinRound];
//? ? 收尾樣式
[path setLineCapStyle:kCGLineCapRound];
3-5.設(shè)置顏色
像lineWidth屬性一樣,我們將lineColor設(shè)置為公開的屬性。然后設(shè)置路徑的顏色為lineColor
@property (nonatomic, strong) UIColor * lineColor;
//? ? 將顏色存儲在對象屬性中
path.pathColor = self.lineColor;
在控制器類中為顏色按鈕拖線,三個(gè)按鈕控件連接同一個(gè)方法。將按鈕的背景顏色傳給view的lineColor;
//設(shè)置顏色
- (IBAction)setLineColor:(UIButton *)sender {
self.paintView.lineColor = sender.backgroundColor;
}
回到MayView.m中
- (void)drawRect:(CGRect)rect {
//? ? 設(shè)置顏色
//? ? [[UIColor blackColor] setStroke];
//? ? 渲染(遍歷數(shù)組中所有路徑,進(jìn)行繪制)
for (MayPath * path in self.allPaths) {
//為路徑渲染相應(yīng)的顏色
[path.pathColor setStroke];
//渲染路徑
[path stroke];
}
}
3-6.開始描線
//? ? 3.設(shè)置起點(diǎn)
//? ? 3-1.獲取點(diǎn)擊對象
UITouch * touch =[touches anyObject];
//? ? 3-2.獲取點(diǎn)擊的位置
CGPoint currentBeganPoint = [touch locationInView:touch.view];
//? ? 4.設(shè)置起點(diǎn)
[path moveToPoint:currentBeganPoint];
//每次單擊都有效果
[path addLineToPoint:currentBeganPoint];
//重繪
[self setNeedsDisplay];
}
//點(diǎn)擊移動(dòng)- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent *)event {
//? ? 1.獲取點(diǎn)擊對象
UITouch * touch = [touches anyObject];
//? ? 2.獲取點(diǎn)擊位置
CGPoint? currentMovedPoint = [touch locationInView:touch.view];
//? ? 3.將點(diǎn)擊起點(diǎn)處 連線 到當(dāng)前點(diǎn)(取得起點(diǎn):就是數(shù)組中的最后一個(gè))
[[self.allPaths lastObject] addLineToPoint:currentMovedPoint];
//? ? 4.重繪/渲染? view
[self setNeedsDisplay];
}
三、清屏、撤銷、橡皮 功能
在MayView.h聲明方法,在.m中實(shí)現(xiàn),在控制器類中為三個(gè)按鈕拖線并且分別調(diào)用各自的方法
//清屏
- (void)clearScreen {
//將路徑數(shù)組清空
[self.allPaths removeAllObjects];
//? ? 重繪 view
[self setNeedsDisplay];
}
//撤銷
- (void)goBack {
//清除路徑最后一條
[self.allPaths removeLastObject];
//? ? 重繪 view
[self setNeedsDisplay];
}
//橡皮
- (void)erase{
self.lineColor = self.backgroundColor;
}
四、保存到相冊功能
4-1.在MayView.h聲明方法,在.m中實(shí)現(xiàn)
4-2.圖片類型的上下文:其實(shí)就是改目標(biāo)。開啟圖片類型的上下文:(默認(rèn)是view,但——只要你開啟了圖片類型的上下文,那么渲染的目標(biāo)就是內(nèi)部的UIImage對象
4-3.以后比較常用的開啟圖片上下文的方法:
UIGraphicsBeginImageContextWithOptions(大小,不透明,縮放)
大小:確定后里面圖片就是這個(gè)大小
縮放:設(shè)置為0就是根據(jù)你的屏幕設(shè)置它的點(diǎn)倍數(shù)
4-4.有開啟圖片上下文就要有關(guān)閉;UIGraphicsEndImageContext
4-5.小技巧
和繪圖有關(guān)的方法都是CGContext開頭
與上下文有關(guān)的方法是UIGraphics開頭
//保存
- (void)saveToPhoto {
//開啟圖片類型的上下文
UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
//圖層內(nèi)容給與到上下文
[self.layer renderInContext:context];
//獲取圖片
UIImage * img = UIGraphicsGetImageFromCurrentImageContext();
//將圖片保存到相冊
UIImageWriteToSavedPhotosAlbum(img, self, @selector(image:didFinishSavingWithError:contextInfo:), nil);
//? ? ? ? 關(guān)閉圖片類型上下文
UIGraphicsEndImageContext();
}
注意:保存到相冊的那個(gè)方法UIImageWriteToSavedPhotosAlbum:它內(nèi)部假如要調(diào)用一個(gè)方法只能是(image:didFinishSavingWithError:contextInfo)。官方文檔有說明;
- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo {
if (error) {
NSLog(@"保存失敗");
}
else NSLog(@"保存成功");
}
在控制器類中為保存按鈕拖線并且調(diào)用方法
//保存
- (IBAction)beSave:(UIBarButtonItem *)sender {
//彈出提示框
UIAlertController * alert = [UIAlertController alertControllerWithTitle:@"將保存到相冊" message:@"請輸入圖片的名字:" preferredStyle:UIAlertControllerStyleAlert];
[alert addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
}];
[self presentViewController:alert animated:YES completion:nil];
//? ? 添加按鈕
UIAlertAction * cancel = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:nil];
UIAlertAction * confirm = [UIAlertAction actionWithTitle:@"確定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
//? ? 文件保存名字
UITextField * tf = alert.textFields[0];
self.paintView.imgName = [NSString stringWithFormat:@"%@.png",tf.text];
[self.paintView saveToPhoto];
}];
[alert addAction:cancel];
[alert addAction:confirm];
}