前言
掌握
1、- (void)drawRect:(CGRect)rect;的使用
2、常见图形的绘制
3、绘图状态的设置:文字颜色、线宽
4、图形上下文状态的保持、恢复
5、图形上下文
6、矩阵操作
7、quartz 2D 的内存管理
8、图片水印、裁剪以及屏幕的截图
一、什么是quartz 2D
quartz 2D是一个二维绘图引擎,同时支持iOS、Mac系统
1、quartz 2D能完成的工作
1》绘制图形、文字
2》绘制、生成图片(图像)
3》读取、生成PDF文件
4》图片的裁截:圆形裁剪
5》自定义控件
2、quartz 2D在iOS开发中的价值
当使用uikit框架的普通控件无法实现需求的时候,可采用quartz 2D技术将 控件内部的结构画出来 ,自定义UI控件的样子
3、 Quartz2D须知
1)Quartz2D的API是纯C语言的
2)Quartz2D的API来自于Core Graphics框架
3)数据类型和函数基本都以CG作为前缀:CGContextRef、CGPathRef、CGContextStrokePath(ctx);
4、 Quartz 2D绘图的基础元素——路径
1)路径定义了一条或者或多条形状或子路径
2)子路径可以包含一条或者多条直线或曲线
3)子路径也可以是一些简单的形状,例如线、圆形、矩形或者星型等
4)子路径还可以包含复杂的形状,例如地图轮廓或者涂鸦等
5)路径可以是开放的,也可以是封闭的
6)路径主要使用在定义视图运动轨迹
4、quartz 2D 的内存管理
1、如果含有create、copy的函数创建对象,使用完之后必须释放,否则将导致内测泄露
2、如果retain了一个对象,不在使用时需将其release掉
1》可以使用quart 2D的函数(e g. CGColorSpaceRetain)来指定retain\release一个对象,或者使用core foundation 的CFRetain.
二、图形上下文(graphics context)
是一个CGContextRef类型数据
1、作用
1》保持绘图的信息
2》决定绘图的输出目标(PDF、bitmap、layer、printer、window)
p s:相同的一套绘图序列,指定不同的Graphics Context,就可将相同的图像绘制到不同的目标上
2、分类
Quartz2D提供了以下几种类型的Graphics Context:
Bitmap Graphics Context
PDF Graphics Context
Window Graphics Context
Layer Graphics Context
Printer Graphics Context
3、 图形上下文栈的操作
//将当前的上下文copy一份,保存到栈顶(那个栈叫做”图形上下文栈”)
void CGContextSaveGState(CGContextRef c)
//将栈顶的上下文出栈,替换掉当前的上下文
void CGContextRestoreGState(CGContextRef c)
- (void)drawRect:(CGRect)rect{
CGContextRef context= UIGraphicsGetCurrentContext();
//Pushes a copy of the current graphics state onto the graphics state stack for the context.
CGContextSaveGState(context);
//设置绘制信息
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(10, 125)];
[path addLineToPoint:CGPointMake(240, 125)];
CGContextAddPath(context, path.CGPath);
//设置绘图状态
[[UIColor redColor] set];
//渲染
CGContextStrokePath(context);
//绘制第二条线
UIBezierPath *path1 = [UIBezierPath bezierPath];
[path1 moveToPoint:CGPointMake(125, 10)];
[path1 addLineToPoint:CGPointMake(125, 240)];
CGContextAddPath(context, path1.CGPath);//Adds a previously created Quartz path object to the current path in a graphics context.
//Sets the current graphics state to the state most recently saved.设置绘图状态为最近保存的上下文状态
CGContextRestoreGState(context);
CGContextStrokePath(context);
}
三、自定义控件
1、如何利用Quartz2D绘制东西到view上?
(1)首先,得有图形上下文,因为它能保存绘图信息,并且决定着绘制到什么地方去
(2)其次,那个图形上下文必须跟view相关联,才能将内容绘制到view上面
2、自定义UI控件的步骤
1)新建一个类,继承自UIView
2)实现- (void)drawRect:(CGRect)rect方法,然后在这个方法中,可以:
(1)取得跟当前view相关联的图形上下文
(2)绘制相应的图形内容,绘制时产生的线条称为路径。 路径由一个或多个直线段或曲线段组成。
(3)利用图形上下文将绘制的所有内容渲染显示到view上面
也可以:利用UIKit封装的绘图函数直接绘图
3、drawRect:方法在什么时候被调用?
(1)当view第一次显示到屏幕上时(被加到UIWindow上显示出来)
(2)调用view的setNeedsDisplay或者setNeedsDisplayInRect:时
4、 drawRect:中取得的上下文(Layer Graphics Context)
在drawRect:方法中取得上下文后,就可以绘制东西到view上
1)View内部有个layer(图层)属性,drawRect:方法中取得的是一个Layer Graphics Context,因此,绘制的东西其实是绘制到view的layer上去了
2)View之所以能显示东西,完全是因为它内部的layer
四、quartz 2D 绘图的代码步骤
1.获得图形上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
2.拼接路径(下面代码是搞一条线段)
//新建一个起点
void CGContextMoveToPoint(CGContextRef c, CGFloat x, CGFloat y)
//添加新的线段到某个点
void CGContextAddLineToPoint(CGContextRef c, CGFloat x, CGFloat y)
//添加一个矩形
void CGContextAddRect(CGContextRef c, CGRect rect)
//添加一个椭圆
void CGContextAddEllipseInRect(CGContextRef context, CGRect rect)
//添加一个圆弧
void CGContextAddArc(CGContextRef c, CGFloat x, CGFloat y, CGFloat radius, CGFloat startAngle, CGFloat endAngle, int
clockwise)
3.绘制路径
//Mode参数决定绘制的模式
void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode)
//绘制空心路径
void CGContextStrokePath(CGContextRef c)
//绘制实心路径
void CGContextFillPath(CGContextRef c)
提示:一般以CGContextDraw、CGContextStroke、CGContextFill开头的函数,都是用来绘制路径的
4、案例 绘制下载进度条
#import "HSProgressView.h"
@interface HSProgressView ()
@property (nonatomic,weak) UILabel *textLable;
@end
@implementation HSProgressView
- (UILabel *)textLable{
if (nil == _textLable) {
UILabel *tmpView = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, 100, 100)];
_textLable = tmpView;
[_textLable setTextAlignment:NSTextAlignmentCenter];
[self addSubview:_textLable];
}
return _textLable;
}
//重写百分比的setter方法,来做一些额外的事情
- (void)setProgressValue:(CGFloat)progressValue{
_progressValue = progressValue;
[self.textLable setText:[NSString stringWithFormat:@"%.2f%%",progressValue*100]];
//绘制进度图形
/**
在view 上做重绘的标志,当下次屏幕刷新的时候会调用drawRect方法
*/
[self setNeedsDisplay];//重绘视图to notify the system that your view’s contents need to be redrawn.
}
/*绘制进度*/
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code
CGContextRef context = UIGraphicsGetCurrentContext();
CGPoint center = CGPointMake(50, 50);
CGFloat radious = 44;
CGFloat startAngle = -M_PI_2;
CGFloat endAngle = startAngle + self.progressValue * M_PI*2;
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radious startAngle:startAngle endAngle:endAngle clockwise:YES];
CGContextAddPath(context, path.CGPath);
//渲染
CGContextStrokePath(context);
}
五、图片水印、裁剪以及屏幕截图
1、图片水印
//1、开启一个基于位图的图形上下文
void UIGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale);
//2.从上下文中取得图片(UIImage)
UIImage* UIGraphicsGetImageFromCurrentImageContext();
//3.结束基于位图的图形上下文
void UIGraphicsEndImageContext();
/*例子*/
//1、开启上下文
UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);
UIImage *image = [UIImage imageNamed:@"img"];
[image drawAtPoint:CGPointZero];
[@"@Lydia" drawAtPoint:CGPointMake(150, 150) withAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:12],NSForegroundColorAttributeName:[UIColor redColor]}];
//2、获取上下文图片
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
//3、结束上下文
UIGraphicsEndImageContext();
//4、显示图片
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
[imageView setImage:newImage];
[self.view addSubview:imageView];
//5、保存图片
// NSData *data = UIImageJPEGRepresentation(newImage, 0.0001);// value 1.0 represents the least compression (or best quality)
NSData *data = UIImagePNGRepresentation(newImage);//将图片转换成二进制数据
[data writeToFile:@"/Users/devzkn/Desktop/lydia.png" atomically:YES];
2、图片裁剪
void CGContextClip(CGContextRef c);//将当前上下所绘制的路径裁剪出来(超出这个裁剪区域的都不能显示)
//方式二、裁剪感兴趣的部分
UIRectClip(textFrame);//Modifies the current clipping path by intersecting it with the specified rectangle.
/*裁剪代码*/
#import "UIImage+Tool.h"
@implementation UIImage (Tool)
+ (UIImage *)imageWithName:(NSString *)name border:(CGFloat)border borderColor:(UIColor *)borderColor{
UIImage *image = [UIImage imageNamed:name];//旧图片
CGFloat borderWidth = border;//圆环高度
//新图片的大小
CGFloat newImageW = image.size.width+borderWidth*2;
CGFloat newImageH = image.size.height+borderWidth*2;
CGFloat circleW = (newImageH>newImageW) ? newImageW:newImageH;//新的图片尺寸
//1、开启上下文
UIGraphicsBeginImageContextWithOptions(CGSizeMake(circleW, circleW), NO, 0.0);
//1>画大圆正切于矩形-----------
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, circleW,circleW)];
//获取当前上下文
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextAddPath(context, path.CGPath);
[borderColor set];
CGContextFillPath(context);
//绘制小圆
//2>画一个正切于旧图片的小圆-----
CGRect clipR = CGRectMake(borderWidth,borderWidth,image.size.width,image.size.height);
UIBezierPath *clipPath = [UIBezierPath bezierPathWithOvalInRect:clipR];
[clipPath addClip];//设置裁剪区域
[image drawAtPoint:CGPointMake(borderWidth, borderWidth)];
//2、获取上下文图片
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
//3、结束上下文
UIGraphicsEndImageContext();
return newImage;
}
3、屏幕截图
核心代码:调用某个view的layer的renderInContext:方法即可
- (void)renderInContext:(CGContextRef)ctx;
UIGraphicsBeginImageContext(self.view.bounds.size);
CGContextRef context = UIGraphicsGetCurrentContext();
//将layer渲染到上下文
[self.view.layer renderInContext:context];
//获取新图片
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
NSData *data = UIImagePNGRepresentation(newImage);
[data writeToFile:@"/Users/devzkn/Desktop/layer.png" atomically:YES];
正文
一、案例
1、饼图
/*pie
寻找规律
*/
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code
CGContextRef context = UIGraphicsGetCurrentContext();
NSArray *data = @[@25,@25,@50];
CGPoint center = CGPointMake(125, 125);
CGFloat radious = 100;//半径
CGFloat startAngle = 0;//起始弧度
CGFloat endAngle = 0;
CGFloat angle = 0;
//开始绘制饼图
for (NSNumber *num in data) {
//绘制
angle =num.floatValue/100.0 *M_PI*2;
startAngle = endAngle;//上一个饼图的结束弧度为下一个饼图的开始弧度
endAngle = startAngle + angle;
UIBezierPath *path2 = [UIBezierPath bezierPathWithArcCenter:center radius:radious startAngle:startAngle endAngle:endAngle clockwise:YES];
[path2 addLineToPoint:center];
CGContextAddPath(context, path2.CGPath);
[[UIColor randomColor]set];
//渲染第i个
CGContextFillPath(context);
}
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self setNeedsDisplay];
}
//生成随机颜色
+ (UIColor *)randomColor{
/*
RGB 24位,RGB 每个颜色通道8位,8 的二进制255,即颜色取值是0~255
RGBA
*/
CGFloat red = arc4random_uniform(256)/255.0;
CGFloat blue = arc4random_uniform(256)/255.0;
CGFloat green = arc4random_uniform(256)/255.0;
return [UIColor colorWithRed:red green:green blue:blue alpha:1];
}
2、柱状图
/* bar*/
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code
CGContextRef context = UIGraphicsGetCurrentContext();
NSArray *data = @[@25,@25,@50];
CGFloat ViewH =CGRectGetHeight(self.frame);
CGFloat width = CGRectGetWidth(self.frame)/(2*data.count-1);
for (int i =0; i<data.count; i++) {
//绘制
CGFloat height = ViewH*([(NSNumber *)data[i] floatValue]/100.0);
CGFloat x = i*width*2;
CGFloat y = ViewH - height ;
UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(x, y, width, height)];
CGContextAddPath(context, path.CGPath);
[[UIColor randomColor] set];
//渲染
CGContextFillPath(context);
}
}
3、UIKit的演练
1》- (void)drawInRect:(CGRect)rect withAttributes:(NSDictionary<NSString *,id> *)attrs
/*
字符属性
字符属性可以应用于 attributed string 的文本中。
NSString *const NSFontAttributeName;(字体)
NSString *const NSParagraphStyleAttributeName;(段落)
NSString *const NSForegroundColorAttributeName;(字体颜色)
NSString *const NSBackgroundColorAttributeName;(字体背景色)
NSString *const NSLigatureAttributeName;(连字符)
NSString *const NSKernAttributeName;(字间距)
NSString *const NSStrikethroughStyleAttributeName;(删除线)
NSString *const NSUnderlineStyleAttributeName;(下划线)
NSString *const NSStrokeColorAttributeName;(边线颜色)
NSString *const NSStrokeWidthAttributeName;(边线宽度)
NSString *const NSShadowAttributeName;(阴影)(横竖排版)
NSString *const NSVerticalGlyphFormAttributeName;
常量
1> NSFontAttributeName(字体)
该属性所对应的值是一个 UIFont 对象。该属性用于改变一段文本的字体。如果不指定该属性,则默认为12-point Helvetica(Neue)。
2> NSParagraphStyleAttributeName(段落)
该属性所对应的值是一个 NSParagraphStyle 对象。该属性在一段文本上应用多个属性。如果不指定该属性,则默认为 NSParagraphStyle 的defaultParagraphStyle 方法返回的默认段落属性。
3> NSForegroundColorAttributeName(字体颜色)
该属性所对应的值是一个 UIColor 对象。该属性用于指定一段文本的字体颜色。如果不指定该属性,则默认为黑色。
4> NSBackgroundColorAttributeName(字体背景色)
该属性所对应的值是一个 UIColor 对象。该属性用于指定一段文本的背景颜色。如果不指定该属性,则默认无背景色。
5> NSLigatureAttributeName(连字符)
该属性所对应的值是一个 NSNumber 对象(整数)。连体字符是指某些连在一起的字符,它们采用单个的图元符号。0 表示没有连体字符。1 表示使用默认的连体字符。2表示使用所有连体符号。默认值为 1(注意,iOS 不支持值为 2)。
6> NSKernAttributeName(字间距)
该属性所对应的值是一个 NSNumber 对象(整数)。字母紧排指定了用于调整字距的像素点数。字母紧排的效果依赖于字体。值为 0 表示不使用字母紧排。默认值为0。
7> NSStrikethroughStyleAttributeName(删除线)
该属性所对应的值是一个 NSNumber 对象(整数)。该值指定是否在文字上加上删除线,该值参考“Underline Style Attributes”。默认值是NSUnderlineStyleNone。
8> NSUnderlineStyleAttributeName(下划线)
该属性所对应的值是一个 NSNumber 对象(整数)。该值指定是否在文字上加上下划线,该值参考“Underline Style Attributes”。默认值是NSUnderlineStyleNone。
9> NSStrokeColorAttributeName(边线颜色)
该属性所对应的值是一个 UIColor 对象。如果该属性不指定(默认),则等同于 NSForegroundColorAttributeName。否则,指定为删除线或下划线颜色。更多细节见“Drawing attributedstrings that are both filled and stroked”。
10> NSStrokeWidthAttributeName(边线宽度)
该属性所对应的值是一个 NSNumber 对象(小数)。该值改变描边宽度(相对于字体size 的百分比)。默认为 0,即不改变。正数只改变描边宽度。负数同时改变文字的描边和填充宽度。例如,对于常见的空心字,这个值通常为3.0。
11> NSShadowAttributeName(阴影)
该属性所对应的值是一个 NSShadow 对象。默认为 nil。
12> NSVerticalGlyphFormAttributeName(横竖排版)
该属性所对应的值是一个 NSNumber 对象(整数)。0 表示横排文本。1 表示竖排文本。在 iOS 中,总是使用横排文本,0 以外的值都未定义。
*/
4、雪花
- (void)drawRect:(CGRect)rect{
if (self.snowY >= CGRectGetHeight(self.frame)) {
self.snowY = 0;
}
self.snowY++;
UIImage *image = [UIImage imageNamed:@"雪花"];
[image drawAtPoint:CGPointMake(0, self.snowY)];
}
- (void)awakeFromNib{
/*
1.NSTimer的精确度就显得低了点,比如NSTimer的触发时间到的时候,runloop如果在阻塞状态,触发时间就会推迟到下一个runloop周期--》导致任务的叠加。
2.CADisplayLink使用场合相对专一,适合做UI的不停重绘,比如自定义动画引擎或者视频播放的渲染。NSTimer的使用范围要广泛的多,各种需要单次或者循环定时处理的任务都可以使用。在UI相关的动画或者显示内容使用 CADisplayLink比起用NSTimer的好处就是我们不需要在格外关心屏幕的刷新频率了,因为它本身就是跟屏幕刷新同步的。
*/
// [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(setNeedsDisplay) userInfo:nil repeats:YES];
/*
IOS设备的屏幕刷新频率是固定的,CADisplayLink在正常情况下会在每次刷新结束都被调用,精确度相当高。
*/
CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(setNeedsDisplay)];
[link addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
}
5、矩阵操作
- (void)drawRect:(CGRect)rect{
CGContextRef context = UIGraphicsGetCurrentContext();
//平移上下文
CGContextTranslateCTM(context, 50, 100);
CGContextRotateCTM(context, M_PI_4);
CGContextScaleCTM(context, 0.2, 0.7);
//上下文矩阵操作,之后在设置绘图信息(路径)
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(-50, -100, 120, 100)];
CGContextAddPath(context, path.CGPath);
[[UIColor yellowColor]set];
CGContextStrokePath(context);
}
6、手势密码
//
// HSLockView.m
// 20140621-手势解锁
//
// Created by devzkn on 4/21/16.
// Copyright © 2016 hisun. All rights reserved.
//
#import "HSLockView.h"
@interface HSLockView ()
@property (nonatomic,strong) NSMutableArray *btns;//存储选中按钮
@property (nonatomic,assign) CGPoint pos;//当前手指的位置
@end
@implementation HSLockView
- (NSMutableArray *)btns{
if (nil == _btns) {
_btns = [NSMutableArray array];
}
return _btns;
}
#if 0
- (void)awakeFromNib{
//绘制九宫格
}
#endif
- (instancetype)initWithCoder:(NSCoder *)aDecoder{
self = [super initWithCoder:aDecoder];
if (self) {
//绘制九宫格按钮
[self addBtns];
}
return self;
}
/**
绘制九宫格
*/
- (void)addBtns{
UIImage *normalImage = [UIImage imageNamed:@"gesture_node_normal"];
UIImage *highlightedImage = [UIImage imageNamed:@"gesture_node_highlighted"];
for (int i=0; i<9; i++) {
//绘制按钮
UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
//设置共性信息
[btn setTag:i];//用于标记按钮
[btn setImage:normalImage forState:UIControlStateNormal];
[btn setImage:highlightedImage forState:UIControlStateSelected];//选中状态要手动管理
/**
处理按钮点击事件:
方式一:addTarget:self
方式二;touches ,设置按钮为不可交互,交给VIew进行处理(事件的传递原理)
*/
//添加监听方法,进行性手动管理选中状态
// [btn addTarget:self action:@selector(clickBtn:) forControlEvents:UIControlEventTouchDown];
[btn setUserInteractionEnabled:NO];
[self addSubview:btn];
}
}
#pragma mark - 点击事件
#if 0
- (void)clickBtn:(UIButton*)btn{
[btn setSelected:YES];
}
#endif
#pragma mark - 使用touches方法 进行按钮的选中处理
/** 获取当前触摸点的坐标*/
- (CGPoint) getPosWithTouches:(NSSet *)touches{
if (touches == nil) {
return CGPointZero;
}
UITouch *touch = [touches anyObject];//Returns one of the objects in the set, or nil if the set contains no objects.
return [touch locationInView:self];//当前触摸点
}
/** 选中按钮的获取*/
- (UIButton *) getButtonWithPos:(CGPoint)pos{
//计算选中范围
CGRect selectRect;
UIButton *posBtn ;
CGFloat wh =30;
for (UIButton *btn in self.subviews) {
CGFloat x = btn.center.x-wh*0.5;
CGFloat y = btn.center.y- wh *0.5;
selectRect = CGRectMake(x, y, wh, wh);
BOOL flg = CGRectContainsPoint(selectRect, pos);
if (flg) {
posBtn = btn;
break;
}
}
return posBtn;
}
#pragma mark - 手指抬起的处理
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
//判断是否解锁成功
NSMutableString *answerStr = [[NSMutableString alloc]init];
for (UIButton *btn in self.btns) {
[answerStr appendFormat:@"%d",btn.tag];
}
NSLog(@"%@",answerStr);
//去除选中状态
[self.btns makeObjectsPerformSelector:@selector(setSelected:) withObject:NO];
//清空轨迹
[self.btns removeAllObjects];
[self setNeedsDisplay];
}
/**控制按钮的选择状态 */
#if 0
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
}
#endif
/** 存储选中按钮*/
- (void) touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
CGPoint pos = [self getPosWithTouches:touches];
[self setPos:pos];
UIButton *btn = [self getButtonWithPos:pos];
if (btn && btn.selected == NO) {
[btn setSelected:YES];
if (![self.btns containsObject:btn]) {
[self.btns addObject:btn];//存储选中按钮
}
}
[self setNeedsDisplay];//绘制
}
#pragma mark - 画线
- (void)drawRect:(CGRect)rect{
if (self.btns.count == 0) {
return;
}
//开始绘制
UIBezierPath *path = [UIBezierPath bezierPath];
for (int i=0; i<self.btns.count; i++) {
UIButton *btn = self.btns[i];
if (i==0) {
[path moveToPoint:btn.center];
}else{
[path addLineToPoint:btn.center];
}
}
[path addLineToPoint:self.pos];
//设置绘图状态
[[UIColor greenColor]set];
[path setLineWidth:10];
[path setLineCapStyle:kCGLineCapRound];
[path setLineJoinStyle:kCGLineJoinRound];
[path stroke];
}
#pragma mark - layoutSubviews 设置子视图的位置信息
- (void)layoutSubviews{
[super layoutSubviews];
//设置位置信息
CGFloat btnWidth = 74;
CGFloat btnHeight = 74;
CGFloat x = 0;
CGFloat y = 0;
int col = 0;//所在的列数
int row = 0;//所在的行数
int tolCol = 3;//总列数
CGFloat viewWidth = CGRectGetWidth(self.frame);
CGFloat viewHeight = CGRectGetHeight(self.frame);
CGFloat rowMargin = (viewWidth - tolCol*btnWidth)*0.25;//间距
CGFloat colMargin = (viewHeight - tolCol*btnHeight)*0.25;//间距
for (int i =0; i<9; i++) {
//绘制
UIButton *btn = self.subviews[i];
//计算所在的列数
col = i % tolCol;
row = i / tolCol;//所在行数
x = col*(rowMargin+btnWidth)+rowMargin;//x 与所在的列相关
y = row*(colMargin+btnHeight)+colMargin;
[btn setFrame:CGRectMake(x, y, btnWidth, btnHeight)];
}
}
@end
7、画板
1》从相册选择照片
从相册选择照片 Expand source
//
// ViewController.m
// 20160421-画板
//
// Created by devzkn on 4/21/16.
// Copyright © 2016 hisun. All rights reserved.
//
#import "ViewController.h"
#import "HSPaintView.h"
#import "HSHandleImageView.h"
@interface ViewController ()<UIImagePickerControllerDelegate,UINavigationControllerDelegate>
@property (weak, nonatomic) IBOutlet HSPaintView *paintView;
@end
@implementation ViewController
- (IBAction)lineWidthValueChange:(UISlider *)sender {
[self.paintView setLineWidth:sender.value];
}
- (IBAction)colorClick:(UIButton *)sender {
[self.paintView setColor:sender.backgroundColor];
}
#pragma mark - 清屏
- (IBAction)cleanScreen:(UIBarButtonItem *)sender {
[self.paintView cleanScreen];
}
- (IBAction)undo:(UIBarButtonItem *)sender {
[self.paintView undo];
}
- (IBAction)eraserClick:(UIBarButtonItem *)sender {
[self.paintView setColor:[UIColor whiteColor]];
}
#pragma mark - 从相册选择照片
- (IBAction)selectedPicture:(UIBarButtonItem*)sender {
//去用户相册
UIImagePickerController *pickerVc = [[UIImagePickerController alloc]init];
/*
UIImagePickerControllerSourceTypePhotoLibrary,相簿,默认值
UIImagePickerControllerSourceTypeCamera,//真机测试
UIImagePickerControllerSourceTypeSavedPhotosAlbum
*/
[pickerVc setSourceType:UIImagePickerControllerSourceTypeSavedPhotosAlbum];//照片
//modal
//设置代理
[pickerVc setDelegate:self];
[self presentViewController:pickerVc animated:YES completion:^{
//
}];
}
#pragma mark - UIImagePickerControllerDelegate
// The picker does not dismiss itself; the client dismisses it in these callbacks.
// The delegate will receive one or the other, but not both, depending whether the user
// confirms or cancels.
#if 0
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)image editingInfo:(nullable NSDictionary<NSString *,id> *)editingInfo{// NS_DEPRECATED_IOS(2_0, 3_0)
NSLog(@"%s",__func__);
}
#endif
/**
选中图片的时候调用
*/
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info{
/**
{
UIImagePickerControllerMediaType = "public.image";
UIImagePickerControllerOriginalImage = "<UIImage: 0x7ae83b30> size {3000, 2002} orientation 0 scale 1.000000";
UIImagePickerControllerReferenceURL = "assets-library://asset/asset.JPG?id=ED7AC36B-A150-4C38-BB8C-B6D696F4F2ED&ext=JPG";
}
*/
UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage];
HSHandleImageView *handleView = [[HSHandleImageView alloc]initWithFrame:self.paintView.frame];
/*
回调,进行传值,定义block的具体内容
*/
[handleView setBlock:^(UIImage *image){
[self.paintView setImage:image];
}];
[handleView setImage:image];//将选中的图片显示到handleView
[self.view addSubview:handleView];
[self dismissViewControllerAnimated:YES completion:^{
//
}];
}
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker{
NSLog(@"%s",__func__);
[self dismissViewControllerAnimated:YES completion:^{
//
}];
}
#pragma mark 保存图片
- (IBAction)save:(UIBarButtonItem *)sender {
[self.paintView save];
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
@end
2》画板的基本功能
画板的辅助功能
//
// HSPaintView.m
// 20160421-画板
//
// Created by devzkn on 4/21/16.
// Copyright © 2016 hisun. All rights reserved.
//
#import "HSPaintView.h"
#import "HSPaintPath.h"
#import "MBProgressHUD+MJ.h"
@interface HSPaintView ()
@property (nonatomic,strong) HSPaintPath *path;//存储当前触摸的路径
@property (nonatomic,strong) NSMutableArray *paths;//存储历史的路径
@end
@implementation HSPaintView
- (void)setImage:(UIImage *)image{
_image = image;
[self.paths addObject:image];
[self setNeedsDisplay];
}
- (void)cleanScreen{
[self.paths removeAllObjects];
[self setNeedsDisplay];
}
- (void)undo{
[self.paths removeLastObject];
[self setNeedsDisplay];
}
- (void)setLineWidth:(CGFloat)lineWidth{
_lineWidth = lineWidth;
}
- (void) setColor:(UIColor *)color{
_color = color;
}
- (NSMutableArray *)paths{
if (nil == _paths) {
_paths = [NSMutableArray array];
}
return _paths;
}
/** 获取触摸点*/
- (CGPoint) pointWithTouches:(NSSet *)touches{
UITouch *touch = [touches anyObject];
return [touch locationInView:self];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.path = [HSPaintPath pathWithColor:self.color lineWidht:self.lineWidth startPoint:[self pointWithTouches:touches]];
[self.paths addObject:self.path];
}
-(void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
//确定终点
[self.path addLineToPoint:[self pointWithTouches:touches]];
[self setNeedsDisplay];
}
#pragma mark - 绘制状态
- (void)drawRect:(CGRect)rect{
if (!self.paths.count) {
return;
}
for (HSPaintPath *path in self.paths) {
if ([path isKindOfClass:[UIImage class]]) {
//画图片
UIImage *image = (UIImage *)path;
[image drawAtPoint:CGPointZero];
}else{
//设置绘制状态
[path.color set];
//描边
[path stroke];
}
}
}
#pragma mark -初始化绘制状态
- (void)awakeFromNib{
self.lineWidth = 2;
}
#pragma mark - 保存到用户相册
- (void)save{
//1.截屏
UIGraphicsBeginImageContext(self.bounds.size);
//渲染
//获取上下文
CGContextRef context = UIGraphicsGetCurrentContext();
[self.layer renderInContext:context];
//2.获取新图片
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
//3.关闭
UIGraphicsEndImageContext();
//4\保存
// NSData *data = UIImagePNGRepresentation(newImage);
// [data writeToFile:@"/Users/devzkn/Desktop/layer.png" atomically:YES];
//保存到用户相册里面
UIImageWriteToSavedPhotosAlbum(newImage, self, @selector(image:didFinishSavingWithError:contextInfo:), context);
}
- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo{
NSLog(@"%s",__func__);
if (error) {
//保存失败
[MBProgressHUD showError:@"保存失败"];
}else{
[MBProgressHUD showSuccess:@"保存成功"];
}
}
3》手势识别器的处理
手势识别器的使用
#if 1
- (instancetype)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
if (self) {
//设置自己的属性
[self addUILongPressGestureRecognizer];
[self addUIRotationGestureRecognizer];
[self addUIPinchGestureRecognizer];
}
return self;
}
#endif
#pragma mark - 添加手势识别器
- (void) addUILongPressGestureRecognizer{
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc]initWithTarget:self action:@selector(longPress:)];
[longPress setDelegate:self];
[self.imageView addGestureRecognizer:longPress];
}
- (void) addUIRotationGestureRecognizer{
UIRotationGestureRecognizer *rotationGR = [[UIRotationGestureRecognizer alloc]initWithTarget:self action:@selector(rotationGR:)];
[rotationGR setDelegate:self];
[self.imageView addGestureRecognizer:rotationGR];
}
#pragma mark - rotation
- (void) rotationGR:(UIRotationGestureRecognizer*)rotationGR{
NSLog(@"%s",__func__);
[self.imageView setTransform:CGAffineTransformRotate(self.imageView.transform, rotationGR.rotation)];
[rotationGR setRotation:0];
}
- (void) addUIPinchGestureRecognizer{
UIPinchGestureRecognizer *pinchGR = [[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(pinchGR:)];
[pinchGR setDelegate:self];
[self.imageView addGestureRecognizer:pinchGR];}
- (void) pinchGR:(UIPinchGestureRecognizer*)pinchGR{
NSLog(@"%s",__func__);
[self.imageView setTransform:CGAffineTransformScale(self.imageView.transform, pinchGR.scale, pinchGR.scale)];
[pinchGR setScale:1];
}
#pragma mark - 长按处理
- (void) longPress:(UILongPressGestureRecognizer*)longPressGR{
if (longPressGR.state == UIGestureRecognizerStateEnded) {
//动画
[UIView animateWithDuration:0.5 animations:^{
//设置透明度
[self.imageView setAlpha:0.3];
} completion:^(BOOL finished) {
//还原透明度
[UIView animateWithDuration:0.5 animations:^{
[self.imageView setAlpha:1.0];
}completion:^(BOOL finished) {
//1、截屏
UIGraphicsBeginImageContext(self.bounds.size);
[self.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *newImage =UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
//2.将图片传递给VC,进行渲染--使用block
self.block(newImage);
//3销毁自己
[self removeFromSuperview];
}];
}];
}
}
- (void)setImage:(UIImage *)image{
_image = image;
[self.imageView setImage:image];
}
#pragma mark - gestureRecognizerDelegate
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
return YES;
}
总结
一、编程规范
1、枚举类型变量通常以K开头
枚举定义 Expand source
/* Line cap styles. */
typedef CF_ENUM(int32_t, CGLineCap) {
kCGLineCapButt,
kCGLineCapRound,
kCGLineCapSquare
};
二、语法
1、字符串格式
1》转义符号%
[self.textLable setText:[NSString stringWithFormat:@"%.2f%%",progressValue*100]];