Quartz2D实现简单“画图板”

概念

1.首先要明白所有iOS中见到的所有东西,都是因为通过UIView内部的图层——CALayer对象;

并且,UIView显示到屏幕上时,会调用【 deawRect: 】方法进行绘图。

即:

》UIView负责:交互、响应;

》CALayer负责:展示、绘图;

但——系统提供的可能无法满足需求,那么我们就需要到Quartz2D(自定义UI控件)了。

2.图形上下文CGContextRef三个作用:

》路径

》状态

》目标

3.drawRect:方法

注意:

》与View相关的【图形上下文】只能在 drawRect:方法中取得,但重写 drawRect:方法需要一个UIView类,控制器类UIViewController中无法得到 drawRect:,往往需要自行创建一个;

》drawRect不能手动调用,有可能获取不到正确的上下文;

》drawRect的调用时机:

   ①、在View第一次显示时;

   ②、重绘:调用【 view的setNeedsDisplay 】或【setNeedsDisplayInRect:】的时候;



步骤:

一、搭建UI界面

二、画笔功能:

1、重写drawRect方法,需创建一个UIView类—— MayView,指定sb (storyboard) 中绘图区View的class为 MayView;

2、在MayView类中创建一个可变数组用于存储所有的路径

//存储路径的数组:路径不只有一条

@property (nonatomic, strong) NSMutableArray *allPaths;

并且进行懒加载

#pragma mark - Getter & Setter

//懒加载

- (NSMutableArray *)allPaths {

if (_allPaths == nil) {

_allPaths = [NSMutableArray array];

}

return _allPaths;

}

3、思考:每一条线都有什么状态,怎么修改?想画出一条线,怎么做?

a》注意:路径的颜色的设置需要在 drawRect:方法中,并且因为每一条线都是独立的,我们可能为每一条线设置不同的颜色等状态。我们需要为每一个路径,创建独立的对象,让它成为一个独立的存在,并且拥有颜色属性,那么我们可以通类对象来设置颜色属性。因此,我们需要创建一个路径类;

@interface MayPath : UIBezierPath

//每条路径自己的颜色状态

@property (nonatomic, strong) UIColor *pathColor;

@end

b》画一条线:至少分两步走:点击 和 移动,所以需要调用touchesBegan方法和touchesMove方法

3-1. 调用方法

在MayView类中引入头文件

#import "MayPath.h"

在“touchesBegan方法”里面创建一条路径,并且添加到全局的路径数组中(为什么每次都创建而不是用全局的?因为画板不可能只点击一次,所以我们要为每一次的点击都创建一个路径)

//开始点击

- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent *)event {

//    1.创建路径

//每次点击时候都创建一条MayPath对象路径

MayPath * path = [MayPath bezierPath];

//    将创建好的路径添加到路径数组中

[self.allPaths addObject:path];

3-2.这一步我们要在MayView.h文件中创建一个lineWidth属性。(后面会将它与sb中的slider控件关联)

//线宽(设为公开属性为外部调用,与slider控件关联)

@property (nonatomic, assign) CGFloat lineWidth;

回到.m文件,设置线宽,将定于好的lineWidth赋值过来

//    2.设置线宽

[path setLineWidth:self.lineWidth];

3-3.我们需要回到控制器类ViewController中,引入自定义MayView类;

将MayView作为属性传入

#import "MayView.h"。

@interface ViewController ()

//拿到自定义view(画板),以拿到画板的属性

@property (weak, nonatomic) IBOutlet MayView *paintView;

@end

然后,为sb中的slider控件拖一条方法连线,并且在属性栏中更改它的值


//设置线宽与UISlider关联

- (IBAction)setLineWidth:(UISlider *)sender {

self.paintView.lineWidth = sender.value;

}

这样,lineWidth的值会随着UISlider控件的变化而变化;

同时我们需要在viewDidLoad中设置线宽的初始值,不设置,一开始没有点击UISlider,那么默认值为0,会无法显示。

- (void)viewDidLoad {

[super viewDidLoad];

//设置线宽的初始值

self.paintView.lineWidth = 1;

}

3-4.回到MayView.m中

//    连接处的样式

[path setLineJoinStyle:kCGLineJoinRound];

//    收尾样式

[path setLineCapStyle:kCGLineCapRound];

3-5.设置颜色

像lineWidth属性一样,我们将lineColor设置为公开的属性。然后设置路径的颜色为lineColor

@property (nonatomic, strong) UIColor * lineColor;

//    将颜色存储在对象属性中

path.pathColor = self.lineColor;

在控制器类中为颜色按钮拖线,三个按钮控件连接同一个方法。将按钮的背景颜色传给view的lineColor;

//设置颜色

- (IBAction)setLineColor:(UIButton *)sender {

self.paintView.lineColor = sender.backgroundColor;

}

回到MayView.m中

- (void)drawRect:(CGRect)rect {

//    设置颜色

//    [[UIColor blackColor] setStroke];

//    渲染(遍历数组中所有路径,进行绘制)

for (MayPath * path in self.allPaths) {

//为路径渲染相应的颜色

[path.pathColor setStroke];

//渲染路径

[path stroke];

}

}

3-6.开始描线

//    3.设置起点

//    3-1.获取点击对象

UITouch * touch =[touches anyObject];

//    3-2.获取点击的位置

CGPoint currentBeganPoint = [touch locationInView:touch.view];

//    4.设置起点

[path moveToPoint:currentBeganPoint];

//每次单击都有效果

[path addLineToPoint:currentBeganPoint];

//重绘

[self setNeedsDisplay];

}

//点击移动- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent *)event {

//    1.获取点击对象

UITouch * touch = [touches anyObject];

//    2.获取点击位置

CGPoint  currentMovedPoint = [touch locationInView:touch.view];

//    3.将点击起点处 连线 到当前点(取得起点:就是数组中的最后一个)

[[self.allPaths lastObject] addLineToPoint:currentMovedPoint];

//    4.重绘/渲染  view

[self setNeedsDisplay];

}

三、清屏、撤销、橡皮 功能

在MayView.h声明方法,在.m中实现,在控制器类中为三个按钮拖线并且分别调用各自的方法

//清屏

- (void)clearScreen {

//将路径数组清空

[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中实现

4-2.图片类型的上下文:其实就是改目标。开启图片类型的上下文:(默认是view,但——只要你开启了图片类型的上下文,那么渲染的目标就是内部的UIImage对象

4-3.以后比较常用的开启图片上下文的方法:

UIGraphicsBeginImageContextWithOptions(大小,不透明,缩放)

大小:确定后里面图片就是这个大小

缩放:设置为0就是根据你的屏幕设置它的点倍数

4-4.有开启图片上下文就要有关闭;UIGraphicsEndImageContext

4-5.小技巧

和绘图有关的方法都是CGContext开头

与上下文有关的方法是UIGraphics开头

//保存

- (void)saveToPhoto {

//开启图片类型的上下文

UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0);

CGContextRef context = UIGraphicsGetCurrentContext();

//图层内容给与到上下文

[self.layer renderInContext:context];

//获取图片

UIImage * img = UIGraphicsGetImageFromCurrentImageContext();

//将图片保存到相册

UIImageWriteToSavedPhotosAlbum(img, self, @selector(image:didFinishSavingWithError:contextInfo:), nil);

//        关闭图片类型上下文

UIGraphicsEndImageContext();

}

注意:保存到相册的那个方法UIImageWriteToSavedPhotosAlbum:它内部假如要调用一个方法只能是(image:didFinishSavingWithError:contextInfo)。官方文档有说明;

- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo {

if (error) {

NSLog(@"保存失败");

}

else NSLog(@"保存成功");

}

在控制器类中为保存按钮拖线并且调用方法

//保存

- (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];

}


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容

  • Quartz2D以及drawRect的重绘机制字数1487 阅读21 评论1 喜欢1一、什么是Quartz2D Q...
    PurpleWind阅读 766评论 0 3
  • Quartz2D 简介及用途 Quartz 2D 是一个二维绘图引擎,同时支持iOS和Mac系统,Quartz2D...
    45b645c5912e阅读 962评论 1 16
  • 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥ios动画全貌。在这里你可以看...
    每天刷两次牙阅读 8,471评论 6 30
  • Quartz2D 简介 Quartz2D是二维(平面)的绘图引擎(经包装的函数库,方便开发者使用。也就是说苹果帮我...
    iOS_Cqlee阅读 628评论 0 2
  • 转载自cocoaChina http://www.cocoachina.com/bbs/read.php?tid...
    wzjmyff阅读 409评论 0 0